diff --git a/libraries/session/src/main/java/androidx/media3/session/LibraryResult.java b/libraries/session/src/main/java/androidx/media3/session/LibraryResult.java index aa1c5c60cde..531c1a86ede 100644 --- a/libraries/session/src/main/java/androidx/media3/session/LibraryResult.java +++ b/libraries/session/src/main/java/androidx/media3/session/LibraryResult.java @@ -21,6 +21,7 @@ import static java.lang.annotation.ElementType.TYPE_USE; import android.annotation.SuppressLint; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.SystemClock; @@ -336,6 +337,7 @@ private static void verifyMediaItem(MediaItem item) { private static final String FIELD_VALUE = Util.intToStringMaxRadix(3); private static final String FIELD_VALUE_TYPE = Util.intToStringMaxRadix(4); private static final String FIELD_SESSION_ERROR = Util.intToStringMaxRadix(5); + private static final String FIELD_IN_PROCESS_BINDER = Util.intToStringMaxRadix(6); // Casting V to ImmutableList is safe if valueType == VALUE_TYPE_ITEM_LIST. @SuppressWarnings("unchecked") @@ -375,6 +377,17 @@ public Bundle toBundle() { return bundle; } + /** + * Returns a {@link Bundle} containing the entirety of this {@link #LibraryResult} object without + * bundling it, for use in local process communication only. + */ + @UnstableApi + public Bundle toBundleForLocalProcess() { + Bundle bundle = new Bundle(); + bundle.putBinder(FIELD_IN_PROCESS_BINDER, new InProcessBinder()); + return bundle; + } + /** Restores a {@code LibraryResult} from a {@link Bundle}. */ // fromBundle will throw if the bundle doesn't have the right value type. @UnstableApi @@ -413,6 +426,13 @@ public static LibraryResult fromUnknownBundle(Bundle bundle) { */ private static LibraryResult fromBundle( Bundle bundle, @Nullable @ValueType Integer expectedType) { + IBinder inProcessBinder = bundle.getBinder(FIELD_IN_PROCESS_BINDER); + if (inProcessBinder instanceof LibraryResult.InProcessBinder) { + LibraryResult result = + ((LibraryResult.InProcessBinder) inProcessBinder).getLibraryResult(); + checkState(expectedType == null || expectedType == result.valueType); + return result; + } int resultCode = bundle.getInt(FIELD_RESULT_CODE, /* defaultValue= */ RESULT_SUCCESS); long completionTimeMs = bundle.getLong(FIELD_COMPLETION_TIME_MS, /* defaultValue= */ SystemClock.elapsedRealtime()); @@ -469,4 +489,10 @@ private static LibraryResult fromBundle( /** The value type isn't known because the result is carrying an error. */ private static final int VALUE_TYPE_ERROR = 4; + + private final class InProcessBinder extends Binder { + public LibraryResult getLibraryResult() { + return LibraryResult.this; + } + } } 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 5c7d4b29ee3..640a0e818b0 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java @@ -2829,7 +2829,12 @@ private void sendControllerResult(int seq, SessionResult result) { return; } try { - iSession.onControllerResult(controllerStub, seq, result.toBundle()); + iSession.onControllerResult( + controllerStub, + seq, + iSession instanceof MediaSessionStub + ? result.toBundleForLocalProcess() + : result.toBundle()); } catch (RemoteException e) { Log.w(TAG, "Error in sending"); } diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java index 0d5c65227c9..5c3cb9060c3 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java @@ -2080,13 +2080,21 @@ public IBinder getCallbackBinder() { @Override public void onSessionResult(int sequenceNumber, SessionResult result) throws RemoteException { - iController.onSessionResult(sequenceNumber, result.toBundle()); + iController.onSessionResult( + sequenceNumber, + iController instanceof MediaControllerStub + ? result.toBundleForLocalProcess() + : result.toBundle()); } @Override public void onLibraryResult(int sequenceNumber, LibraryResult result) throws RemoteException { - iController.onLibraryResult(sequenceNumber, result.toBundle()); + iController.onLibraryResult( + sequenceNumber, + iController instanceof MediaControllerStub + ? result.toBundleForLocalProcess() + : result.toBundle()); } @Override @@ -2221,11 +2229,14 @@ public void onPeriodicSessionPositionInfoChanged( boolean canAccessTimeline, int controllerInterfaceVersion) throws RemoteException { + SessionPositionInfo filteredPositionInfo = + sessionPositionInfo.filterByAvailableCommands( + canAccessCurrentMediaItem, canAccessTimeline); iController.onPeriodicSessionPositionInfoChanged( sequenceNumber, - sessionPositionInfo - .filterByAvailableCommands(canAccessCurrentMediaItem, canAccessTimeline) - .toBundle(controllerInterfaceVersion)); + iController instanceof MediaControllerStub + ? filteredPositionInfo.toBundleForLocalProcess() + : filteredPositionInfo.toBundle(controllerInterfaceVersion)); } @Override diff --git a/libraries/session/src/main/java/androidx/media3/session/SessionPositionInfo.java b/libraries/session/src/main/java/androidx/media3/session/SessionPositionInfo.java index ed9addf32a5..fdaa2846daf 100644 --- a/libraries/session/src/main/java/androidx/media3/session/SessionPositionInfo.java +++ b/libraries/session/src/main/java/androidx/media3/session/SessionPositionInfo.java @@ -17,7 +17,9 @@ import static androidx.media3.common.util.Assertions.checkArgument; +import android.os.Binder; import android.os.Bundle; +import android.os.IBinder; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.media3.common.C; @@ -169,6 +171,8 @@ public String toString() { @VisibleForTesting static final String FIELD_CONTENT_BUFFERED_POSITION_MS = Util.intToStringMaxRadix(9); + private static final String FIELD_IN_PROCESS_BINDER = Util.intToStringMaxRadix(10); + /** * Returns a copy of this session position info, filtered by the specified available commands. * @@ -234,8 +238,18 @@ public Bundle toBundle(int controllerInterfaceVersion) { return bundle; } + public Bundle toBundleForLocalProcess() { + Bundle bundle = new Bundle(); + bundle.putBinder(FIELD_IN_PROCESS_BINDER, new InProcessBinder()); + return bundle; + } + /** Restores a {@code SessionPositionInfo} from a {@link Bundle}. */ public static SessionPositionInfo fromBundle(Bundle bundle) { + IBinder inProcessBinder = bundle.getBinder(FIELD_IN_PROCESS_BINDER); + if (inProcessBinder instanceof InProcessBinder) { + return ((InProcessBinder) inProcessBinder).getSessionPositionInfo(); + } @Nullable Bundle positionInfoBundle = bundle.getBundle(FIELD_POSITION_INFO); PositionInfo positionInfo = positionInfoBundle == null @@ -267,4 +281,10 @@ public static SessionPositionInfo fromBundle(Bundle bundle) { contentDurationMs, contentBufferedPositionMs); } + + private final class InProcessBinder extends Binder { + public SessionPositionInfo getSessionPositionInfo() { + return SessionPositionInfo.this; + } + } } diff --git a/libraries/session/src/main/java/androidx/media3/session/SessionResult.java b/libraries/session/src/main/java/androidx/media3/session/SessionResult.java index 02a2398b636..7bd9fdf5042 100644 --- a/libraries/session/src/main/java/androidx/media3/session/SessionResult.java +++ b/libraries/session/src/main/java/androidx/media3/session/SessionResult.java @@ -19,7 +19,9 @@ import static java.lang.annotation.ElementType.TYPE_USE; import android.annotation.SuppressLint; +import android.os.Binder; import android.os.Bundle; +import android.os.IBinder; import android.os.SystemClock; import androidx.annotation.IntDef; import androidx.annotation.Nullable; @@ -241,6 +243,7 @@ private SessionResult( private static final String FIELD_EXTRAS = Util.intToStringMaxRadix(1); private static final String FIELD_COMPLETION_TIME_MS = Util.intToStringMaxRadix(2); private static final String FIELD_SESSION_ERROR = Util.intToStringMaxRadix(3); + private static final String FIELD_IN_PROCESS_BINDER = Util.intToStringMaxRadix(4); @UnstableApi public Bundle toBundle() { @@ -254,9 +257,24 @@ public Bundle toBundle() { return bundle; } + /** + * Returns a {@link Bundle} containing the entirety of this {@link #SessionResult} object without + * bundling it, for use in local process communication only. + */ + @UnstableApi + public Bundle toBundleForLocalProcess() { + Bundle bundle = new Bundle(); + bundle.putBinder(FIELD_IN_PROCESS_BINDER, new InProcessBinder()); + return bundle; + } + /** Restores a {@code SessionResult} from a {@link Bundle}. */ @UnstableApi public static SessionResult fromBundle(Bundle bundle) { + IBinder inProcessBinder = bundle.getBinder(FIELD_IN_PROCESS_BINDER); + if (inProcessBinder instanceof InProcessBinder) { + return ((InProcessBinder) inProcessBinder).getSessionResult(); + } int resultCode = bundle.getInt(FIELD_RESULT_CODE, /* defaultValue= */ SessionError.ERROR_UNKNOWN); @Nullable Bundle extras = bundle.getBundle(FIELD_EXTRAS); @@ -274,4 +292,10 @@ public static SessionResult fromBundle(Bundle bundle) { return new SessionResult( resultCode, extras == null ? Bundle.EMPTY : extras, completionTimeMs, sessionError); } + + private final class InProcessBinder extends Binder { + public SessionResult getSessionResult() { + return SessionResult.this; + } + } } diff --git a/libraries/session/src/test/java/androidx/media3/session/LibraryResultTest.java b/libraries/session/src/test/java/androidx/media3/session/LibraryResultTest.java index 50a656d0da6..9e29719d48b 100644 --- a/libraries/session/src/test/java/androidx/media3/session/LibraryResultTest.java +++ b/libraries/session/src/test/java/androidx/media3/session/LibraryResultTest.java @@ -145,4 +145,14 @@ public void toBundle_roundTrip_equalsWithOriginal() { assertThat(errorLibraryResultFromBundle.completionTimeMs) .isEqualTo(errorLibraryResult.completionTimeMs); } + + @Test + public void roundTripViaBundleForLocalProcess_yieldsSameInstance() { + LibraryResult errorLibraryResult = + LibraryResult.ofError(new SessionError(ERROR_NOT_SUPPORTED, "error message", new Bundle())); + LibraryResult unbundledLibraryResult = + LibraryResult.fromUnknownBundle(errorLibraryResult.toBundleForLocalProcess()); + + assertThat(errorLibraryResult == unbundledLibraryResult).isTrue(); + } } diff --git a/libraries/session/src/test/java/androidx/media3/session/SessionPositionInfoTest.java b/libraries/session/src/test/java/androidx/media3/session/SessionPositionInfoTest.java index df17bee0077..3f0d561f598 100644 --- a/libraries/session/src/test/java/androidx/media3/session/SessionPositionInfoTest.java +++ b/libraries/session/src/test/java/androidx/media3/session/SessionPositionInfoTest.java @@ -60,6 +60,14 @@ public void roundTripViaBundle_yieldsEqualInstance() { assertThat(sessionPositionInfo).isEqualTo(testSessionPositionInfo); } + @Test + public void roundTripViaBundleForLocalProcess_yieldsSameInstance() { + SessionPositionInfo roundTripValue = + SessionPositionInfo.fromBundle(SessionPositionInfo.DEFAULT.toBundleForLocalProcess()); + + assertThat(SessionPositionInfo.DEFAULT == roundTripValue).isTrue(); + } + @Test public void constructor_invalidIsPlayingAd_throwsIllegalArgumentException() { Assert.assertThrows( diff --git a/libraries/session/src/test/java/androidx/media3/session/SessionResultTest.java b/libraries/session/src/test/java/androidx/media3/session/SessionResultTest.java index 4aa878f3650..f9a9fb70567 100644 --- a/libraries/session/src/test/java/androidx/media3/session/SessionResultTest.java +++ b/libraries/session/src/test/java/androidx/media3/session/SessionResultTest.java @@ -15,6 +15,7 @@ */ package androidx.media3.session; +import static androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED; import static androidx.media3.session.SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED; import static androidx.media3.session.SessionError.ERROR_SESSION_CONCURRENT_STREAM_LIMIT; import static com.google.common.truth.Truth.assertThat; @@ -72,4 +73,14 @@ public void toBundle_roundTrip_resultsInEqualObjectWithSameBundle() { assertThat(resultFromBundle.sessionError.extras.getString("errorKey")).isEqualTo("errorValue"); assertThat(resultFromBundle.extras.size()).isEqualTo(0); } + + @Test + public void roundTripViaBundleForLocalProcess_yieldsSameInstance() { + SessionResult errorSessionResult = + new SessionResult(new SessionError(ERROR_NOT_SUPPORTED, "error message", new Bundle())); + SessionResult unbundledSessionResult = + SessionResult.fromBundle(errorSessionResult.toBundleForLocalProcess()); + + assertThat(errorSessionResult == unbundledSessionResult).isTrue(); + } }