Commit 5528baaa by bachinger Committed by christosts

Do not assume a valid queue in 3rd party sessions

This change fixes an issue that can be reproduced when
a controller `onConnect` creates a `QueueTimeline` out
of the state of a legacy session and then `prepare` is called.

`activeQueueItemId`, `metadata` and the `queue` of the legacy
session are used when a `QueueTimeline` is created. The change
adds unit tests to cover the different combinatoric cases these
properties being set or unset.

PiperOrigin-RevId: 505731288
(cherry picked from commit 4a9cf7d0)
parent bfc4ed4d
...@@ -54,6 +54,8 @@ ...@@ -54,6 +54,8 @@
onto Player ([#156](https://github.com/androidx/media/issues/156)). onto Player ([#156](https://github.com/androidx/media/issues/156)).
* Avoid double tap detection for non-Bluetooth media button events * Avoid double tap detection for non-Bluetooth media button events
([#233](https://github.com/androidx/media/issues/233)). ([#233](https://github.com/androidx/media/issues/233)).
* Make `QueueTimeline` more robust in case of a shady legacy session state
([#241](https://github.com/androidx/media/issues/241)).
* Metadata: * Metadata:
* Parse multiple null-separated values from ID3 frames, as permitted by * Parse multiple null-separated values from ID3 frames, as permitted by
ID3 v2.4. ID3 v2.4.
......
...@@ -1828,6 +1828,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -1828,6 +1828,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
+ " MediaItem."); + " MediaItem.");
MediaItem fakeMediaItem = MediaItem fakeMediaItem =
MediaUtils.convertToMediaItem(newLegacyPlayerInfo.mediaMetadataCompat, ratingType); MediaUtils.convertToMediaItem(newLegacyPlayerInfo.mediaMetadataCompat, ratingType);
// Ad a tag to make sure the fake media item can't have an equal instance by accident.
fakeMediaItem = fakeMediaItem.buildUpon().setTag(new Object()).build();
currentTimeline = currentTimeline.copyWithFakeMediaItem(fakeMediaItem); currentTimeline = currentTimeline.copyWithFakeMediaItem(fakeMediaItem);
currentMediaItemIndex = currentTimeline.getWindowCount() - 1; currentMediaItemIndex = currentTimeline.getWindowCount() - 1;
} else { } else {
...@@ -1842,7 +1844,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -1842,7 +1844,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
if (hasMediaMetadataCompat) { if (hasMediaMetadataCompat) {
MediaItem mediaItem = MediaItem mediaItem =
MediaUtils.convertToMediaItem( MediaUtils.convertToMediaItem(
currentTimeline.getMediaItemAt(currentMediaItemIndex).mediaId, checkNotNull(currentTimeline.getMediaItemAt(currentMediaItemIndex)).mediaId,
newLegacyPlayerInfo.mediaMetadataCompat, newLegacyPlayerInfo.mediaMetadataCompat,
ratingType); ratingType);
currentTimeline = currentTimeline =
...@@ -1999,7 +2001,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -1999,7 +2001,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
MediaItem oldCurrentMediaItem = MediaItem oldCurrentMediaItem =
checkStateNotNull(oldControllerInfo.playerInfo.getCurrentMediaItem()); checkStateNotNull(oldControllerInfo.playerInfo.getCurrentMediaItem());
int oldCurrentMediaItemIndexInNewTimeline = int oldCurrentMediaItemIndexInNewTimeline =
((QueueTimeline) newControllerInfo.playerInfo.timeline).findIndexOf(oldCurrentMediaItem); ((QueueTimeline) newControllerInfo.playerInfo.timeline).indexOf(oldCurrentMediaItem);
if (oldCurrentMediaItemIndexInNewTimeline == C.INDEX_UNSET) { if (oldCurrentMediaItemIndexInNewTimeline == C.INDEX_UNSET) {
// Old current item is removed. // Old current item is removed.
discontinuityReason = Player.DISCONTINUITY_REASON_REMOVE; discontinuityReason = Player.DISCONTINUITY_REASON_REMOVE;
......
...@@ -42,4 +42,5 @@ interface IRemoteMediaSessionCompat { ...@@ -42,4 +42,5 @@ interface IRemoteMediaSessionCompat {
void sendSessionEvent(String sessionTag, String event, in Bundle extras); void sendSessionEvent(String sessionTag, String event, in Bundle extras);
void setCaptioningEnabled(String sessionTag, boolean enabled); void setCaptioningEnabled(String sessionTag, boolean enabled);
void setSessionExtras(String sessionTag, in Bundle extras); void setSessionExtras(String sessionTag, in Bundle extras);
int getCallbackMethodCount(String sessionTag, String methodName);
} }
...@@ -49,9 +49,13 @@ import java.util.concurrent.Executor; ...@@ -49,9 +49,13 @@ import java.util.concurrent.Executor;
@UnstableApi @UnstableApi
public class MediaSessionCompatProviderService extends Service { public class MediaSessionCompatProviderService extends Service {
public static final String METHOD_ON_PREPARE_FROM_MEDIA_ID = "onPrepareFromMediaId";
public static final String METHOD_ON_PREPARE = "onPrepare";
private static final String TAG = "MSCProviderService"; private static final String TAG = "MSCProviderService";
Map<String, MediaSessionCompat> sessionMap = new HashMap<>(); Map<String, MediaSessionCompat> sessionMap = new HashMap<>();
Map<String, CallCountingCallback> callbackMap = new HashMap<>();
RemoteMediaSessionCompatStub sessionBinder; RemoteMediaSessionCompatStub sessionBinder;
TestHandler handler; TestHandler handler;
...@@ -88,7 +92,10 @@ public class MediaSessionCompatProviderService extends Service { ...@@ -88,7 +92,10 @@ public class MediaSessionCompatProviderService extends Service {
() -> { () -> {
MediaSessionCompat session = MediaSessionCompat session =
new MediaSessionCompat(MediaSessionCompatProviderService.this, sessionTag); new MediaSessionCompat(MediaSessionCompatProviderService.this, sessionTag);
CallCountingCallback callback = new CallCountingCallback(sessionTag);
session.setCallback(callback);
sessionMap.put(sessionTag, session); sessionMap.put(sessionTag, session);
callbackMap.put(sessionTag, callback);
}); });
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Exception occurred while creating MediaSessionCompat", e); Log.e(TAG, "Exception occurred while creating MediaSessionCompat", e);
...@@ -212,15 +219,61 @@ public class MediaSessionCompatProviderService extends Service { ...@@ -212,15 +219,61 @@ public class MediaSessionCompatProviderService extends Service {
} }
@Override @Override
public void setCaptioningEnabled(String sessionTag, boolean enabled) throws RemoteException { public void setCaptioningEnabled(String sessionTag, boolean enabled) {
MediaSessionCompat session = sessionMap.get(sessionTag); MediaSessionCompat session = sessionMap.get(sessionTag);
session.setCaptioningEnabled(enabled); session.setCaptioningEnabled(enabled);
} }
@Override @Override
public void setSessionExtras(String sessionTag, Bundle extras) throws RemoteException { public void setSessionExtras(String sessionTag, Bundle extras) {
MediaSessionCompat session = sessionMap.get(sessionTag); MediaSessionCompat session = sessionMap.get(sessionTag);
session.setExtras(extras); session.setExtras(extras);
} }
@Override
public int getCallbackMethodCount(String sessionTag, String methodName) {
CallCountingCallback callCountingCallback = callbackMap.get(sessionTag);
if (callCountingCallback != null) {
Integer count = callCountingCallback.callbackCallCounters.get(methodName);
return count != null ? count : 0;
}
return 0;
}
}
private class CallCountingCallback extends MediaSessionCompat.Callback {
private final String sessionTag;
private final Map<String, Integer> callbackCallCounters;
public CallCountingCallback(String sessionTag) {
this.sessionTag = sessionTag;
callbackCallCounters = new HashMap<>();
}
@Override
public void onPrepareFromMediaId(String mediaId, Bundle extras) {
countCallbackCall(METHOD_ON_PREPARE_FROM_MEDIA_ID);
sessionMap
.get(sessionTag)
.setMetadata(
new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaId)
.build());
}
@Override
public void onPrepare() {
countCallbackCall(METHOD_ON_PREPARE);
sessionMap.get(sessionTag).setMetadata(new MediaMetadataCompat.Builder().build());
}
private void countCallbackCall(String callbackName) {
int count = 0;
if (callbackCallCounters.containsKey(callbackName)) {
count = callbackCallCounters.get(callbackName);
}
callbackCallCounters.put(callbackName, ++count);
}
} }
} }
...@@ -111,6 +111,10 @@ public class RemoteMediaSessionCompat { ...@@ -111,6 +111,10 @@ public class RemoteMediaSessionCompat {
binder.setPlaybackToLocal(sessionTag, stream); binder.setPlaybackToLocal(sessionTag, stream);
} }
public int getCallbackMethodCount(String callbackMethodName) throws RemoteException {
return binder.getCallbackMethodCount(sessionTag, callbackMethodName);
}
/** /**
* Since we cannot pass VolumeProviderCompat directly, we pass volumeControl, maxVolume, * Since we cannot pass VolumeProviderCompat directly, we pass volumeControl, maxVolume,
* currentVolume instead. * currentVolume instead.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment