Fix NPE in MediaController's constructor

MediaController tries to release itself when the binder to the
session becomes unavailable. However, if such thing happens while
connecting in the constructor, it causes NPE when accessing
MediaControllerImpl which is only initialized after it's
connected.

This fixes random failures in existing tests.

PiperOrigin-RevId: 421423381
This commit is contained in:
jaewan 2022-01-13 00:33:26 +00:00 committed by Ian Baker
parent 9a4ad05586
commit 7756d9823d
4 changed files with 26 additions and 5 deletions

View file

@ -374,6 +374,7 @@ public class MediaController implements Player {
@Initialized
MediaController thisRef = this;
impl = thisRef.createImpl(context, thisRef, token, connectionHints);
impl.connect();
}
/* package */ MediaControllerImpl createImpl(
@ -1823,6 +1824,8 @@ public class MediaController implements Player {
interface MediaControllerImpl {
void connect();
void addListener(Player.Listener listener);
void removeListener(Player.Listener listener);

View file

@ -166,6 +166,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
private final Context context;
private final SessionToken token;
private final Bundle connectionHints;
private final IBinder.DeathRecipient deathRecipient;
private final SurfaceCallback surfaceCallback;
private final ListenerSet<Listener> listeners;
@ -211,12 +212,24 @@ import org.checkerframework.checker.nullness.qual.NonNull;
sequencedFutureManager = new SequencedFutureManager();
controllerStub = new MediaControllerStub(this);
this.token = token;
this.connectionHints = connectionHints;
deathRecipient =
() ->
MediaControllerImplBase.this.instance.runOnApplicationLooper(
MediaControllerImplBase.this.instance::release);
surfaceCallback = new SurfaceCallback();
serviceConnection =
(this.token.getType() == TYPE_SESSION)
? null
: new SessionServiceConnection(connectionHints);
flushCommandQueueHandler = new FlushCommandQueueHandler(instance.getApplicationLooper());
lastReturnedContentPositionMs = C.TIME_UNSET;
lastSetPlayWhenReadyCalledTimeMs = C.TIME_UNSET;
}
@Override
public void connect() {
boolean connectionRequested;
if (this.token.getType() == TYPE_SESSION) {
// Session
@ -229,9 +242,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
if (!connectionRequested) {
this.instance.runOnApplicationLooper(MediaControllerImplBase.this.instance::release);
}
flushCommandQueueHandler = new FlushCommandQueueHandler(instance.getApplicationLooper());
lastReturnedContentPositionMs = C.TIME_UNSET;
lastSetPlayWhenReadyCalledTimeMs = C.TIME_UNSET;
}
@Override

View file

@ -155,9 +155,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
this.instance = instance;
controllerCompatCallback = new ControllerCompatCallback();
this.token = token;
}
@Override
public void connect() {
if (this.token.getType() == SessionToken.TYPE_SESSION) {
browserCompat = null;
connectToSession((MediaSessionCompat.Token) checkStateNotNull(this.token.getBinder()));
} else {
connectToService();

View file

@ -38,6 +38,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
@ -117,11 +118,15 @@ public class MediaSessionAndControllerTest {
Handler mainHandler = new Handler(Looper.getMainLooper());
Executor mainExecutor = (runnable) -> Util.postOrRun(mainHandler, runnable);
for (int i = 0; i < 100; i++) {
int idx = i;
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player).setId(TAG).build());
CountDownLatch latch = new CountDownLatch(1);
// Keep the instance in AtomicReference to prevent GC.
// Otherwise ListenableFuture and corresponding controllers can be GCed and disconnected
// callback can be ignored.
AtomicReference<ListenableFuture<MediaController>> controllerFutureRef =
new AtomicReference<>();
mainHandler.post(
() -> {
ListenableFuture<MediaController> controllerFuture =
@ -134,6 +139,7 @@ public class MediaSessionAndControllerTest {
}
})
.buildAsync();
controllerFutureRef.set(controllerFuture);
controllerFuture.addListener(
() -> {
try {