Ensure pending commands are still sent in MediaController.release()
We currently clear all pending messages, including the one that flushes pending commands to the MediaSession. To ensure all commands that have been called before controller.release() are still sent, we can manually trigger the flush message from the release call. Related to handling the final flush because disconnecting the controller, MediaSessionStub didn't post the removal of the controller to the session thread, creating a race condition between removing the controller and actually handling the flush. Issue: androidx/media#99 PiperOrigin-RevId: 462342860
This commit is contained in:
parent
287c757944
commit
ee209690cb
@ -25,6 +25,8 @@
|
|||||||
channel name used by the provider. Also, add method
|
channel name used by the provider. Also, add method
|
||||||
`DefaultNotificationProvider.setSmallIcon(int)` to set the notifications
|
`DefaultNotificationProvider.setSmallIcon(int)` to set the notifications
|
||||||
small icon ([#104](https://github.com/androidx/media/issues/104)).
|
small icon ([#104](https://github.com/androidx/media/issues/104)).
|
||||||
|
* Ensure commands sent before `MediaController.release()` are not dropped
|
||||||
|
([#99](https://github.com/androidx/media/issues/99)).
|
||||||
|
|
||||||
### 1.0.0-beta02 (2022-07-15)
|
### 1.0.0-beta02 (2022-07-15)
|
||||||
|
|
||||||
|
@ -320,7 +320,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||||||
serviceConnection = null;
|
serviceConnection = null;
|
||||||
}
|
}
|
||||||
connectedToken = null;
|
connectedToken = null;
|
||||||
flushCommandQueueHandler.removeCallbacksAndMessages(/* token= */ null);
|
flushCommandQueueHandler.release();
|
||||||
this.iSession = null;
|
this.iSession = null;
|
||||||
controllerStub.destroy();
|
controllerStub.destroy();
|
||||||
if (iSession != null) {
|
if (iSession != null) {
|
||||||
@ -3070,30 +3070,43 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FlushCommandQueueHandler extends Handler {
|
private class FlushCommandQueueHandler {
|
||||||
|
|
||||||
private static final int MSG_FLUSH_COMMAND_QUEUE = 1;
|
private static final int MSG_FLUSH_COMMAND_QUEUE = 1;
|
||||||
|
|
||||||
public FlushCommandQueueHandler(Looper looper) {
|
private final Handler handler;
|
||||||
super(looper);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public FlushCommandQueueHandler(Looper looper) {
|
||||||
public void handleMessage(Message msg) {
|
handler = new Handler(looper, /* callback= */ this::handleMessage);
|
||||||
if (msg.what == MSG_FLUSH_COMMAND_QUEUE) {
|
|
||||||
try {
|
|
||||||
iSession.flushCommandQueue(controllerStub);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Log.w(TAG, "Error in sending flushCommandQueue");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendFlushCommandQueueMessage() {
|
public void sendFlushCommandQueueMessage() {
|
||||||
if (iSession != null && !hasMessages(MSG_FLUSH_COMMAND_QUEUE)) {
|
if (iSession != null && !handler.hasMessages(MSG_FLUSH_COMMAND_QUEUE)) {
|
||||||
// Send message to notify the end of the transaction. It will be handled when the current
|
// Send message to notify the end of the transaction. It will be handled when the current
|
||||||
// looper iteration is over.
|
// looper iteration is over.
|
||||||
sendEmptyMessage(MSG_FLUSH_COMMAND_QUEUE);
|
handler.sendEmptyMessage(MSG_FLUSH_COMMAND_QUEUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
if (handler.hasMessages(MSG_FLUSH_COMMAND_QUEUE)) {
|
||||||
|
flushCommandQueue();
|
||||||
|
}
|
||||||
|
handler.removeCallbacksAndMessages(/* token= */ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleMessage(Message msg) {
|
||||||
|
if (msg.what == MSG_FLUSH_COMMAND_QUEUE) {
|
||||||
|
flushCommandQueue();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushCommandQueue() {
|
||||||
|
try {
|
||||||
|
iSession.flushCommandQueue(controllerStub);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.w(TAG, "Error in sending flushCommandQueue");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -552,7 +552,13 @@ import java.util.concurrent.ExecutionException;
|
|||||||
}
|
}
|
||||||
long token = Binder.clearCallingIdentity();
|
long token = Binder.clearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
connectedControllersManager.removeController(caller.asBinder());
|
@Nullable MediaSessionImpl sessionImpl = this.sessionImpl.get();
|
||||||
|
if (sessionImpl == null || sessionImpl.isReleased()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
postOrRun(
|
||||||
|
sessionImpl.getApplicationHandler(),
|
||||||
|
() -> connectedControllersManager.removeController(caller.asBinder()));
|
||||||
} finally {
|
} finally {
|
||||||
Binder.restoreCallingIdentity(token);
|
Binder.restoreCallingIdentity(token);
|
||||||
}
|
}
|
||||||
|
@ -225,4 +225,27 @@ public class MediaSessionAndControllerTest {
|
|||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
assertThat(playWhenReadyRef.get()).isEqualTo(testPlayWhenReady);
|
assertThat(playWhenReadyRef.get()).isEqualTo(testPlayWhenReady);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void commandBeforeControllerRelease_handledBySession() throws Exception {
|
||||||
|
MockPlayer player =
|
||||||
|
new MockPlayer.Builder().setApplicationLooper(Looper.getMainLooper()).build();
|
||||||
|
MediaSession session =
|
||||||
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
|
new MediaSession.Builder(context, player).setId(TAG).build());
|
||||||
|
MediaController controller = controllerTestRule.createController(session.getToken());
|
||||||
|
|
||||||
|
threadTestRule
|
||||||
|
.getHandler()
|
||||||
|
.postAndSync(
|
||||||
|
() -> {
|
||||||
|
controller.prepare();
|
||||||
|
controller.play();
|
||||||
|
controller.release();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert these methods are called without timing out.
|
||||||
|
player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS);
|
||||||
|
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user