Skip to content

Commit 03f0b53

Browse files
toniheiicbaker
authored andcommittedNov 24, 2022
Add helper method to convert platform session token to Media3 token
This avoids that apps have to depend on the legacy compat support library when they want to make this conversion. Also add a version to both helper methods that takes a Looper to give apps the option to use an existing Looper, which should be much faster than spinning up a new thread for every method call. Issue: #171 PiperOrigin-RevId: 490441913
1 parent 1803d1c commit 03f0b53

File tree

6 files changed

+107
-36
lines changed

6 files changed

+107
-36
lines changed
 

‎RELEASENOTES.md

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ Release notes
1414
([#10604](https://github.com/google/ExoPlayer/issues/10604)).
1515
* Add `ExoPlayer.Builder.setPlaybackLooper` that sets a pre-existing
1616
playback thread for a new ExoPlayer instance.
17+
* Session:
18+
* Add helper method to convert platform session token to Media3
19+
`SessionToken` ([#171](https://github.com/androidx/media/issues/171)).
1720
* Remove deprecated symbols:
1821
* Remove `DefaultAudioSink` constructors, use `DefaultAudioSink.Builder`
1922
instead.

‎libraries/session/src/main/java/androidx/media3/session/MediaSession.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -807,11 +807,10 @@ public ListenableFuture<SessionResult> sendCustomCommand(
807807

808808
/**
809809
* Returns the {@link MediaSessionCompat.Token} of the {@link MediaSessionCompat} created
810-
* internally by this session. You may cast the {@link Object} to {@link
811-
* MediaSessionCompat.Token}.
810+
* internally by this session.
812811
*/
813812
@UnstableApi
814-
public Object getSessionCompatToken() {
813+
public MediaSessionCompat.Token getSessionCompatToken() {
815814
return impl.getSessionCompat().getSessionToken();
816815
}
817816

‎libraries/session/src/main/java/androidx/media3/session/MediaStyleNotificationHelper.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -501,8 +501,7 @@ public static Notification.MediaStyle fillInMediaStyle(
501501
if (actionsToShowInCompact != null) {
502502
setShowActionsInCompactView(style, actionsToShowInCompact);
503503
}
504-
MediaSessionCompat.Token legacyToken =
505-
(MediaSessionCompat.Token) session.getSessionCompatToken();
504+
MediaSessionCompat.Token legacyToken = session.getSessionCompatToken();
506505
style.setMediaSession((android.media.session.MediaSession.Token) legacyToken.getToken());
507506
return style;
508507
}

‎libraries/session/src/main/java/androidx/media3/session/SessionToken.java

+72-26
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@
2828
import android.os.Bundle;
2929
import android.os.Handler;
3030
import android.os.HandlerThread;
31+
import android.os.Looper;
3132
import android.os.ResultReceiver;
3233
import android.support.v4.media.session.MediaControllerCompat;
3334
import android.support.v4.media.session.MediaSessionCompat;
3435
import android.text.TextUtils;
3536
import androidx.annotation.IntDef;
3637
import androidx.annotation.Nullable;
38+
import androidx.annotation.RequiresApi;
3739
import androidx.media.MediaBrowserServiceCompat;
3840
import androidx.media3.common.Bundleable;
3941
import androidx.media3.common.C;
@@ -258,37 +260,86 @@ public Bundle getExtras() {
258260
}
259261

260262
/**
261-
* Creates a token from {@link MediaSessionCompat.Token}.
263+
* Creates a token from a {@link android.media.session.MediaSession.Token}.
262264
*
263-
* @return a {@link ListenableFuture} of {@link SessionToken}
265+
* @param context A {@link Context}.
266+
* @param token The {@link android.media.session.MediaSession.Token}.
267+
* @return A {@link ListenableFuture} for the {@link SessionToken}.
264268
*/
269+
@SuppressWarnings("UnnecessarilyFullyQualified") // Avoiding clash with Media3 MediaSession.
265270
@UnstableApi
271+
@RequiresApi(21)
266272
public static ListenableFuture<SessionToken> createSessionToken(
267-
Context context, Object compatToken) {
268-
checkNotNull(context, "context must not be null");
269-
checkNotNull(compatToken, "compatToken must not be null");
270-
checkArgument(compatToken instanceof MediaSessionCompat.Token);
273+
Context context, android.media.session.MediaSession.Token token) {
274+
return createSessionToken(context, MediaSessionCompat.Token.fromToken(token));
275+
}
271276

277+
/**
278+
* Creates a token from a {@link android.media.session.MediaSession.Token}.
279+
*
280+
* @param context A {@link Context}.
281+
* @param token The {@link android.media.session.MediaSession.Token}.
282+
* @param completionLooper The {@link Looper} on which the returned {@link ListenableFuture}
283+
* completes. This {@link Looper} can't be used to call {@code future.get()} on the returned
284+
* {@link ListenableFuture}.
285+
* @return A {@link ListenableFuture} for the {@link SessionToken}.
286+
*/
287+
@SuppressWarnings("UnnecessarilyFullyQualified") // Avoiding clash with Media3 MediaSession.
288+
@UnstableApi
289+
@RequiresApi(21)
290+
public static ListenableFuture<SessionToken> createSessionToken(
291+
Context context, android.media.session.MediaSession.Token token, Looper completionLooper) {
292+
return createSessionToken(context, MediaSessionCompat.Token.fromToken(token), completionLooper);
293+
}
294+
295+
/**
296+
* Creates a token from a {@link MediaSessionCompat.Token}.
297+
*
298+
* @param context A {@link Context}.
299+
* @param compatToken The {@link MediaSessionCompat.Token}.
300+
* @return A {@link ListenableFuture} for the {@link SessionToken}.
301+
*/
302+
@UnstableApi
303+
public static ListenableFuture<SessionToken> createSessionToken(
304+
Context context, MediaSessionCompat.Token compatToken) {
272305
HandlerThread thread = new HandlerThread("SessionTokenThread");
273306
thread.start();
307+
ListenableFuture<SessionToken> tokenFuture =
308+
createSessionToken(context, compatToken, thread.getLooper());
309+
tokenFuture.addListener(thread::quit, MoreExecutors.directExecutor());
310+
return tokenFuture;
311+
}
312+
313+
/**
314+
* Creates a token from a {@link MediaSessionCompat.Token}.
315+
*
316+
* @param context A {@link Context}.
317+
* @param compatToken The {@link MediaSessionCompat.Token}.
318+
* @param completionLooper The {@link Looper} on which the returned {@link ListenableFuture}
319+
* completes. This {@link Looper} can't be used to call {@code future.get()} on the returned
320+
* {@link ListenableFuture}.
321+
* @return A {@link ListenableFuture} for the {@link SessionToken}.
322+
*/
323+
@UnstableApi
324+
public static ListenableFuture<SessionToken> createSessionToken(
325+
Context context, MediaSessionCompat.Token compatToken, Looper completionLooper) {
326+
checkNotNull(context, "context must not be null");
327+
checkNotNull(compatToken, "compatToken must not be null");
274328

275329
SettableFuture<SessionToken> future = SettableFuture.create();
276330
// Try retrieving media3 token by connecting to the session.
277-
MediaControllerCompat controller =
278-
createMediaControllerCompat(context, (MediaSessionCompat.Token) compatToken);
331+
MediaControllerCompat controller = new MediaControllerCompat(context, compatToken);
279332
String packageName = controller.getPackageName();
280-
Handler handler = new Handler(thread.getLooper());
333+
Handler handler = new Handler(completionLooper);
281334
Runnable createFallbackLegacyToken =
282335
() -> {
283336
int uid = getUid(context.getPackageManager(), packageName);
284337
SessionToken resultToken =
285-
new SessionToken(
286-
(MediaSessionCompat.Token) compatToken,
287-
packageName,
288-
uid,
289-
controller.getSessionInfo());
338+
new SessionToken(compatToken, packageName, uid, controller.getSessionInfo());
290339
future.set(resultToken);
291340
};
341+
// Post creating a fallback token if the command receives no result after a timeout.
342+
handler.postDelayed(createFallbackLegacyToken, WAIT_TIME_MS_FOR_SESSION3_TOKEN);
292343
controller.sendCommand(
293344
MediaConstants.SESSION_COMMAND_REQUEST_SESSION3_TOKEN,
294345
/* params= */ null,
@@ -306,17 +357,13 @@ protected void onReceiveResult(int resultCode, Bundle resultData) {
306357
}
307358
}
308359
});
309-
// Post creating a fallback token if the command receives no result after a timeout.
310-
handler.postDelayed(createFallbackLegacyToken, WAIT_TIME_MS_FOR_SESSION3_TOKEN);
311-
future.addListener(() -> thread.quit(), MoreExecutors.directExecutor());
312360
return future;
313361
}
314362

315363
/**
316-
* Returns a {@link ImmutableSet} of {@link SessionToken} for media session services; {@link
317-
* MediaSessionService}, {@link MediaLibraryService}, and {@link MediaBrowserServiceCompat}
318-
* regardless of their activeness. This set represents media apps that publish {@link
319-
* MediaSession}.
364+
* Returns an {@link ImmutableSet} of {@linkplain SessionToken session tokens} for media session
365+
* services; {@link MediaSessionService}, {@link MediaLibraryService}, and {@link
366+
* MediaBrowserServiceCompat} regardless of their activeness.
320367
*
321368
* <p>The app targeting API level 30 or higher must include a {@code <queries>} element in their
322369
* manifest to get service tokens of other apps. See the following example and <a
@@ -334,6 +381,8 @@ protected void onReceiveResult(int resultCode, Bundle resultData) {
334381
* </intent>
335382
* }</pre>
336383
*/
384+
// We ask the app to declare the <queries> tags, so it's expected that they are missing.
385+
@SuppressWarnings("QueryPermissionsNeeded")
337386
public static ImmutableSet<SessionToken> getAllServiceTokens(Context context) {
338387
PackageManager pm = context.getPackageManager();
339388
List<ResolveInfo> services = new ArrayList<>();
@@ -370,6 +419,8 @@ public static ImmutableSet<SessionToken> getAllServiceTokens(Context context) {
370419
return sessionServiceTokens.build();
371420
}
372421

422+
// We ask the app to declare the <queries> tags, so it's expected that they are missing.
423+
@SuppressWarnings("QueryPermissionsNeeded")
373424
private static boolean isInterfaceDeclared(
374425
PackageManager manager, String serviceInterface, ComponentName serviceComponent) {
375426
Intent serviceIntent = new Intent(serviceInterface);
@@ -402,11 +453,6 @@ private static int getUid(PackageManager manager, String packageName) {
402453
}
403454
}
404455

405-
private static MediaControllerCompat createMediaControllerCompat(
406-
Context context, MediaSessionCompat.Token sessionToken) {
407-
return new MediaControllerCompat(context, sessionToken);
408-
}
409-
410456
/* package */ interface SessionTokenImpl extends Bundleable {
411457

412458
boolean isLegacySession();

‎libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerWithFrameworkMediaSessionTest.java

+2-5
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import android.media.session.PlaybackState;
2727
import android.os.Build;
2828
import android.os.HandlerThread;
29-
import android.support.v4.media.session.MediaSessionCompat;
3029
import androidx.media3.common.Player;
3130
import androidx.media3.common.Player.State;
3231
import androidx.media3.common.util.Util;
@@ -94,8 +93,7 @@ public void cleanUp() {
9493
@Test
9594
public void createController() throws Exception {
9695
SessionToken token =
97-
SessionToken.createSessionToken(
98-
context, MediaSessionCompat.Token.fromToken(fwkSession.getSessionToken()))
96+
SessionToken.createSessionToken(context, fwkSession.getSessionToken())
9997
.get(TIMEOUT_MS, MILLISECONDS);
10098
MediaController controller =
10199
new MediaController.Builder(context, token)
@@ -111,8 +109,7 @@ public void onPlaybackStateChanged_isNotifiedByFwkSessionChanges() throws Except
111109
AtomicInteger playbackStateRef = new AtomicInteger();
112110
AtomicBoolean playWhenReadyRef = new AtomicBoolean();
113111
SessionToken token =
114-
SessionToken.createSessionToken(
115-
context, MediaSessionCompat.Token.fromToken(fwkSession.getSessionToken()))
112+
SessionToken.createSessionToken(context, fwkSession.getSessionToken())
116113
.get(TIMEOUT_MS, MILLISECONDS);
117114
MediaController controller =
118115
new MediaController.Builder(context, token)

‎libraries/test_session_current/src/androidTest/java/androidx/media3/session/SessionTokenTest.java

+27
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@
2020
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
2121
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
2222
import static com.google.common.truth.Truth.assertThat;
23+
import static org.junit.Assume.assumeTrue;
2324

2425
import android.content.ComponentName;
2526
import android.content.Context;
2627
import android.os.Bundle;
2728
import android.os.Process;
2829
import android.support.v4.media.session.MediaSessionCompat;
2930
import androidx.media3.common.MediaLibraryInfo;
31+
import androidx.media3.common.util.Util;
3032
import androidx.media3.test.session.common.HandlerThreadTestRule;
3133
import androidx.media3.test.session.common.MainLooperTestRule;
3234
import androidx.media3.test.session.common.TestUtils;
@@ -68,6 +70,7 @@ public void constructor_sessionService() {
6870
context,
6971
new ComponentName(
7072
context.getPackageName(), MockMediaSessionService.class.getCanonicalName()));
73+
7174
assertThat(token.getPackageName()).isEqualTo(context.getPackageName());
7275
assertThat(token.getUid()).isEqualTo(Process.myUid());
7376
assertThat(token.getType()).isEqualTo(SessionToken.TYPE_SESSION_SERVICE);
@@ -80,6 +83,7 @@ public void constructor_libraryService() {
8083
ComponentName testComponentName =
8184
new ComponentName(
8285
context.getPackageName(), MockMediaLibraryService.class.getCanonicalName());
86+
8387
SessionToken token = new SessionToken(context, testComponentName);
8488

8589
assertThat(token.getPackageName()).isEqualTo(context.getPackageName());
@@ -110,15 +114,36 @@ public void getters_whenCreatedBySession() {
110114
assertThat(token.getServiceName()).isEmpty();
111115
}
112116

117+
@Test
118+
public void createSessionToken_withPlatformTokenFromMedia1Session_returnsTokenForLegacySession()
119+
throws Exception {
120+
assumeTrue(Util.SDK_INT >= 21);
121+
122+
MediaSessionCompat sessionCompat =
123+
sessionTestRule.ensureReleaseAfterTest(
124+
new MediaSessionCompat(context, "createSessionToken_withLegacyToken"));
125+
126+
SessionToken token =
127+
SessionToken.createSessionToken(
128+
context,
129+
(android.media.session.MediaSession.Token)
130+
sessionCompat.getSessionToken().getToken())
131+
.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
132+
133+
assertThat(token.isLegacySession()).isTrue();
134+
}
135+
113136
@Test
114137
public void createSessionToken_withCompatTokenFromMedia1Session_returnsTokenForLegacySession()
115138
throws Exception {
116139
MediaSessionCompat sessionCompat =
117140
sessionTestRule.ensureReleaseAfterTest(
118141
new MediaSessionCompat(context, "createSessionToken_withLegacyToken"));
142+
119143
SessionToken token =
120144
SessionToken.createSessionToken(context, sessionCompat.getSessionToken())
121145
.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
146+
122147
assertThat(token.isLegacySession()).isTrue();
123148
}
124149

@@ -150,6 +175,7 @@ public void getSessionServiceTokens() {
150175
ComponentName mockBrowserServiceCompatName =
151176
new ComponentName(
152177
SUPPORT_APP_PACKAGE_NAME, MockMediaBrowserServiceCompat.class.getCanonicalName());
178+
153179
Set<SessionToken> serviceTokens =
154180
SessionToken.getAllServiceTokens(ApplicationProvider.getApplicationContext());
155181
for (SessionToken token : serviceTokens) {
@@ -162,6 +188,7 @@ public void getSessionServiceTokens() {
162188
hasMockLibraryService2 = true;
163189
}
164190
}
191+
165192
assertThat(hasMockBrowserServiceCompat).isTrue();
166193
assertThat(hasMockSessionService2).isTrue();
167194
assertThat(hasMockLibraryService2).isTrue();

0 commit comments

Comments
 (0)