Deflake MediaSessionServiceTest

`onConnect_controllerInfo_sameInstanceFromServiceToConnectedControllerManager`
was flaky because `onDestroy()` of `MockMediaSessionService` was cleaning
up the `TestServiceRegistry`. This includes releasing the session of the service
after which no further `MediaSession.Callback` methods are called. This created
a race between `Callback.onDisconnected` and the `Mediasession.release()` being
called.

`onDestroy()` is called unexpectedly early because the controller unbinds as the
last controller from the service that never was started (`onStartCommand` never
called). In such a case the service is immediately terminated by the system after
the last client unbinds from the service, which called `onDestroy()` on the mock
service.

This change making `MockMediaSessionService` optionally not clean up the
registry in `onDestroy()` prevents the session from being released too early.
Alternatively, starting playback and hence starting the service into the
foreground would prevent the problem as well for the purpose of the test.
Another alternative to fix the test would be removing the session from the
service before releasing the controller.

PiperOrigin-RevId: 730361199
(cherry picked from commit fbe186a70ccb588889d0909ccfc07affaf49ec51)
This commit is contained in:
bachinger 2025-02-24 02:21:18 -08:00 committed by oceanjules
parent 4d50e8a815
commit 8ee2532db3
2 changed files with 24 additions and 3 deletions

View File

@ -101,6 +101,7 @@ public class MediaSessionServiceTest {
List<ControllerInfo> playbackCommandControllerInfos = new ArrayList<>();
List<ControllerInfo> onDisconnectedCommandControllerInfos = new ArrayList<>();
AtomicReference<MediaSession> session = new AtomicReference<>();
ConditionVariable disconnected = new ConditionVariable();
testServiceRegistry.setOnGetSessionHandler(
controllerInfo -> {
// The controllerInfo passed to the onGetSession of the service.
@ -136,8 +137,8 @@ public class MediaSessionServiceTest {
if (!session.isMediaNotificationController(controller)) {
// The controllerInfo when disconnecting.
onDisconnectedCommandControllerInfos.add(controller);
disconnected.open();
}
MediaSession.Callback.super.onDisconnected(session, controller);
}
})
.build());
@ -150,6 +151,8 @@ public class MediaSessionServiceTest {
// Get the started service instance after creation.
MockMediaSessionService service =
(MockMediaSessionService) testServiceRegistry.getServiceInstance();
// TestServiceRegistry is taken care of and cleaned up @After the test.
service.setCleanupServiceRegistryOnDestroy(false);
controller.setRepeatMode(Player.REPEAT_MODE_ONE);
List<ControllerInfo> connectedControllerManagerControllerInfos = new ArrayList<>();
for (ControllerInfo controllerInfo : session.get().getConnectedControllers()) {
@ -159,9 +162,12 @@ public class MediaSessionServiceTest {
}
}
// The controller that was bound to the service unbinds when released. Because the service was
// never started (as in `onStartCommand()` was never called), the service is immediately
// terminated by the system when the last bound client unbinds.
controller.release();
service.blockUntilAllControllersUnbind(TIMEOUT_MS);
assertThat(disconnected.block(TIMEOUT_MS)).isTrue();
assertThat(onGetSessionControllerInfos).hasSize(1);
assertThat(onGetSessionControllerInfos).isEqualTo(onConnectControllerInfos);
assertThat(onGetSessionControllerInfos).isEqualTo(playbackCommandControllerInfos);

View File

@ -38,11 +38,24 @@ public class MockMediaSessionService extends MediaSessionService {
@Nullable public MediaSession session;
@Nullable private HandlerThread handlerThread;
private boolean cleanupServiceRegistryOnDestroy;
public MockMediaSessionService() {
boundControllerCount = new AtomicInteger(/* initialValue= */ 0);
allControllersUnbound = new ConditionVariable();
allControllersUnbound.open();
cleanupServiceRegistryOnDestroy = true;
}
/**
* Whether the service should clean up the service registry {@link #onDestroy()} by calling {@link
* TestServiceRegistry#cleanUp()} on {@link TestServiceRegistry#getInstance()}.
*
* <p>The cleanup will release all sessions of the service. A test can clean up when tearing down
* the test, to prevent the sessions to be released by the service.
*/
public void setCleanupServiceRegistryOnDestroy(boolean cleanupServiceRegistryOnDestroy) {
this.cleanupServiceRegistryOnDestroy = cleanupServiceRegistryOnDestroy;
}
/** Returns whether at least one controller is bound to this service. */
@ -90,7 +103,9 @@ public class MockMediaSessionService extends MediaSessionService {
@Override
public void onDestroy() {
super.onDestroy();
TestServiceRegistry.getInstance().cleanUp();
if (cleanupServiceRegistryOnDestroy) {
TestServiceRegistry.getInstance().cleanUp();
}
handlerThread.quitSafely();
}