Slightly disentangle MediaBrowser/Controller(Impl)Base/Legacy

These constructors are currently very intertwined, passing `this`
references from the constructor of one to the constructor of another
before the first constructor is complete (and so the `this` reference
isn't really valid yet).

This change uses checker framework `@UnderInitialization` and
`@NotOnlyInitialized` annotations to make it more clear that the
references are not available yet. For the one 'direct' access needed
in the second constructor (calling `getApplicationLooper()`) we now
pass the `applicationLooper` directly alongside (to avoid needing to
dereference the reference 'too early').

This change also ensures that where a class hierarchy has a
'dependent' class hierarchy, the 'subclass' instance is always used
(by both subclass and superclass) without casting or manually hiding
the superclass field, by defining an overridable `getFoo()` method
instead and always using it.

#minor-release

PiperOrigin-RevId: 462335043
(cherry picked from commit 287c757944720c6804e8f1cc074e28134f7da528)
This commit is contained in:
ibaker 2022-07-21 09:20:33 +00:00 committed by microkatz
parent 06d3c07a1c
commit 0667c74dc5
7 changed files with 241 additions and 162 deletions

View File

@ -38,6 +38,9 @@ import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.Executor;
import org.checkerframework.checker.initialization.qual.NotOnlyInitialized;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Browses media content offered by a {@link MediaLibraryService} in addition to the {@link
@ -45,12 +48,6 @@ import java.util.concurrent.Executor;
*/
public final class MediaBrowser extends MediaController {
private static final String WRONG_THREAD_ERROR_MESSAGE =
"MediaBrowser method is called from a wrong thread."
+ " See javadoc of MediaController for details.";
private final MediaBrowserImpl impl;
/** A builder for {@link MediaBrowser}. */
public static final class Builder {
@ -201,6 +198,12 @@ public final class MediaBrowser extends MediaController {
@Nullable LibraryParams params) {}
}
private static final String WRONG_THREAD_ERROR_MESSAGE =
"MediaBrowser method is called from a wrong thread."
+ " See javadoc of MediaController for details.";
@NotOnlyInitialized private @MonotonicNonNull MediaBrowserImpl impl;
/** Creates an instance from the {@link SessionToken}. */
/* package */ MediaBrowser(
Context context,
@ -210,17 +213,24 @@ public final class MediaBrowser extends MediaController {
Looper applicationLooper,
ConnectionCallback connectionCallback) {
super(context, token, connectionHints, listener, applicationLooper, connectionCallback);
this.impl = (MediaBrowserImpl) super.impl;
}
@Override
/* package */ MediaBrowserImpl createImpl(
Context context, MediaController thisRef, SessionToken token, Bundle connectionHints) {
/* package */ @UnderInitialization
MediaBrowserImpl createImpl(
@UnderInitialization MediaBrowser this,
Context context,
SessionToken token,
Bundle connectionHints,
Looper applicationLooper) {
MediaBrowserImpl impl;
if (token.isLegacySession()) {
return new MediaBrowserImplLegacy(context, (MediaBrowser) thisRef, token);
impl = new MediaBrowserImplLegacy(context, this, token, applicationLooper);
} else {
return new MediaBrowserImplBase(context, thisRef, token, connectionHints);
impl = new MediaBrowserImplBase(context, this, token, connectionHints, applicationLooper);
}
this.impl = impl;
return impl;
}
/**
@ -234,7 +244,7 @@ public final class MediaBrowser extends MediaController {
public ListenableFuture<LibraryResult<MediaItem>> getLibraryRoot(@Nullable LibraryParams params) {
verifyApplicationThread();
if (isConnected()) {
return impl.getLibraryRoot(params);
return checkNotNull(impl).getLibraryRoot(params);
}
return createDisconnectedFuture();
}
@ -254,7 +264,7 @@ public final class MediaBrowser extends MediaController {
verifyApplicationThread();
checkNotEmpty(parentId, "parentId must not be empty");
if (isConnected()) {
return impl.subscribe(parentId, params);
return checkNotNull(impl).subscribe(parentId, params);
}
return createDisconnectedFuture();
}
@ -273,7 +283,7 @@ public final class MediaBrowser extends MediaController {
verifyApplicationThread();
checkNotEmpty(parentId, "parentId must not be empty");
if (isConnected()) {
return impl.unsubscribe(parentId);
return checkNotNull(impl).unsubscribe(parentId);
}
return createDisconnectedFuture();
}
@ -299,7 +309,7 @@ public final class MediaBrowser extends MediaController {
checkArgument(page >= 0, "page must not be negative");
checkArgument(pageSize >= 1, "pageSize must not be less than 1");
if (isConnected()) {
return impl.getChildren(parentId, page, pageSize, params);
return checkNotNull(impl).getChildren(parentId, page, pageSize, params);
}
return createDisconnectedFuture();
}
@ -316,7 +326,7 @@ public final class MediaBrowser extends MediaController {
verifyApplicationThread();
checkNotEmpty(mediaId, "mediaId must not be empty");
if (isConnected()) {
return impl.getItem(mediaId);
return checkNotNull(impl).getItem(mediaId);
}
return createDisconnectedFuture();
}
@ -338,7 +348,7 @@ public final class MediaBrowser extends MediaController {
verifyApplicationThread();
checkNotEmpty(query, "query must not be empty");
if (isConnected()) {
return impl.search(query, params);
return checkNotNull(impl).search(query, params);
}
return createDisconnectedFuture();
}
@ -365,7 +375,7 @@ public final class MediaBrowser extends MediaController {
checkArgument(page >= 0, "page must not be negative");
checkArgument(pageSize >= 1, "pageSize must not be less than 1");
if (isConnected()) {
return impl.getSearchResult(query, page, pageSize, params);
return checkNotNull(impl).getSearchResult(query, page, pageSize, params);
}
return createDisconnectedFuture();
}

View File

@ -28,6 +28,7 @@ import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_UNSUBS
import android.content.Context;
import android.os.Bundle;
import android.os.Looper;
import android.os.RemoteException;
import androidx.annotation.Nullable;
import androidx.media3.common.MediaItem;
@ -37,18 +38,27 @@ import androidx.media3.session.SequencedFutureManager.SequencedFuture;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
/** Base implementation of MediaBrowser. */
/* package */ class MediaBrowserImplBase extends MediaControllerImplBase
implements MediaBrowser.MediaBrowserImpl {
private final MediaBrowser instance;
MediaBrowserImplBase(
Context context, MediaController instance, SessionToken token, Bundle connectionHints) {
super(context, instance, token, connectionHints);
Context context,
@UnderInitialization MediaBrowser instance,
SessionToken token,
Bundle connectionHints,
Looper applicationLooper) {
super(context, instance, token, connectionHints, applicationLooper);
this.instance = instance;
}
MediaBrowser getMediaBrowser() {
return (MediaBrowser) instance;
@Override
/* package */ MediaBrowser getInstance() {
return instance;
}
@Override
@ -157,10 +167,10 @@ import com.google.common.util.concurrent.ListenableFuture;
if (!isConnected()) {
return;
}
getMediaBrowser()
getInstance()
.notifyBrowserListener(
listener ->
listener.onSearchResultChanged(getMediaBrowser(), query, itemCount, libraryParams));
listener.onSearchResultChanged(getInstance(), query, itemCount, libraryParams));
}
void notifyChildrenChanged(
@ -168,10 +178,10 @@ import com.google.common.util.concurrent.ListenableFuture;
if (!isConnected()) {
return;
}
getMediaBrowser()
getInstance()
.notifyBrowserListener(
listener ->
listener.onChildrenChanged(getMediaBrowser(), parentId, itemCount, libraryParams));
listener.onChildrenChanged(getInstance(), parentId, itemCount, libraryParams));
}
private <V> ListenableFuture<LibraryResult<V>> dispatchRemoteLibrarySessionTask(

View File

@ -22,6 +22,7 @@ import static androidx.media3.session.LibraryResult.RESULT_ERROR_UNKNOWN;
import android.content.Context;
import android.os.Bundle;
import android.os.Looper;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaBrowserCompat.ItemCallback;
import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback;
@ -38,6 +39,7 @@ import com.google.common.util.concurrent.SettableFuture;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
/** Implementation of MediaBrowser with the {@link MediaBrowserCompat} for legacy support. */
/* package */ class MediaBrowserImplLegacy extends MediaControllerImplLegacy
@ -49,12 +51,20 @@ import java.util.List;
private final HashMap<String, List<SubscribeCallback>> subscribeCallbacks = new HashMap<>();
MediaBrowserImplLegacy(Context context, MediaBrowser instance, SessionToken token) {
super(context, instance, token);
private final MediaBrowser instance;
MediaBrowserImplLegacy(
Context context,
@UnderInitialization MediaBrowser instance,
SessionToken token,
Looper applicationLooper) {
super(context, instance, token, applicationLooper);
this.instance = instance;
}
MediaBrowser getMediaBrowser() {
return (MediaBrowser) instance;
@Override
/* package*/ MediaBrowser getInstance() {
return instance;
}
@Override
@ -78,7 +88,8 @@ import java.util.List;
@Override
public ListenableFuture<LibraryResult<MediaItem>> getLibraryRoot(@Nullable LibraryParams params) {
if (!instance.isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT)) {
if (!getInstance()
.isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED));
}
SettableFuture<LibraryResult<MediaItem>> result = SettableFuture.create();
@ -103,7 +114,7 @@ import java.util.List;
@Override
public ListenableFuture<LibraryResult<Void>> subscribe(
String parentId, @Nullable LibraryParams params) {
if (!instance.isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE)) {
if (!getInstance().isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED));
}
MediaBrowserCompat browserCompat = getBrowserCompat();
@ -124,7 +135,7 @@ import java.util.List;
@Override
public ListenableFuture<LibraryResult<Void>> unsubscribe(String parentId) {
if (!instance.isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE)) {
if (!getInstance().isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED));
}
MediaBrowserCompat browserCompat = getBrowserCompat();
@ -148,7 +159,8 @@ import java.util.List;
@Override
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getChildren(
String parentId, int page, int pageSize, @Nullable LibraryParams params) {
if (!instance.isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_CHILDREN)) {
if (!getInstance()
.isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_CHILDREN)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED));
}
MediaBrowserCompat browserCompat = getBrowserCompat();
@ -164,7 +176,7 @@ import java.util.List;
@Override
public ListenableFuture<LibraryResult<MediaItem>> getItem(String mediaId) {
if (!instance.isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM)) {
if (!getInstance().isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED));
}
MediaBrowserCompat browserCompat = getBrowserCompat();
@ -196,7 +208,7 @@ import java.util.List;
@Override
public ListenableFuture<LibraryResult<Void>> search(
String query, @Nullable LibraryParams params) {
if (!instance.isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_SEARCH)) {
if (!getInstance().isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_SEARCH)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED));
}
MediaBrowserCompat browserCompat = getBrowserCompat();
@ -210,7 +222,7 @@ import java.util.List;
@Override
public void onSearchResult(
String query, Bundle extras, List<MediaBrowserCompat.MediaItem> items) {
getMediaBrowser()
getInstance()
.notifyBrowserListener(
listener -> {
// Set extra null here, because 'extra' have different meanings between old
@ -218,20 +230,20 @@ import java.util.List;
// - Old API: Extra/Option specified with search().
// - New API: Extra from MediaLibraryService to MediaBrowser
// TODO(b/193193565): Cache search result for later getSearchResult() calls.
listener.onSearchResultChanged(getMediaBrowser(), query, items.size(), null);
listener.onSearchResultChanged(getInstance(), query, items.size(), null);
});
}
@Override
public void onError(String query, Bundle extras) {
getMediaBrowser()
getInstance()
.notifyBrowserListener(
listener -> {
// Set extra null here, because 'extra' have different meanings between old
// API and new API as follows.
// - Old API: Extra/Option specified with search().
// - New API: Extra from MediaLibraryService to MediaBrowser
listener.onSearchResultChanged(getMediaBrowser(), query, 0, null);
listener.onSearchResultChanged(getInstance(), query, 0, null);
});
}
});
@ -242,8 +254,8 @@ import java.util.List;
@Override
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getSearchResult(
String query, int page, int pageSize, @Nullable LibraryParams params) {
if (!instance.isSessionCommandAvailable(
SessionCommand.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT)) {
if (!getInstance()
.isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED));
}
MediaBrowserCompat browserCompat = getBrowserCompat();
@ -401,11 +413,11 @@ import java.util.List;
LibraryParams params =
MediaUtils.convertToLibraryParams(
context, browserCompat.getNotifyChildrenChangedOptions());
getMediaBrowser()
getInstance()
.notifyBrowserListener(
listener -> {
// TODO(b/193193565): Cache children result for later getChildren() calls.
listener.onChildrenChanged(getMediaBrowser(), parentId, itemCount, params);
listener.onChildrenChanged(getInstance(), parentId, itemCount, params);
});
future.set(LibraryResult.ofVoid());
}

View File

@ -64,7 +64,8 @@ import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import org.checkerframework.checker.initialization.qual.Initialized;
import org.checkerframework.checker.initialization.qual.NotOnlyInitialized;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
/**
* A controller that interacts with a {@link MediaSession}, a {@link MediaSessionService} hosting a
@ -375,7 +376,7 @@ public class MediaController implements Player {
private boolean released;
/* package */ final MediaControllerImpl impl;
@NotOnlyInitialized private final MediaControllerImpl impl;
/* package */ final Listener listener;
@ -407,19 +408,21 @@ public class MediaController implements Player {
applicationHandler = new Handler(applicationLooper);
this.connectionCallback = connectionCallback;
@SuppressWarnings("nullness:assignment")
@Initialized
MediaController thisRef = this;
impl = thisRef.createImpl(context, thisRef, token, connectionHints);
impl = createImpl(context, token, connectionHints, applicationLooper);
impl.connect();
}
/* package */ MediaControllerImpl createImpl(
Context context, MediaController thisRef, SessionToken token, Bundle connectionHints) {
/* package */ @UnderInitialization
MediaControllerImpl createImpl(
@UnderInitialization MediaController this,
Context context,
SessionToken token,
Bundle connectionHints,
Looper applicationLooper) {
if (token.isLegacySession()) {
return new MediaControllerImplLegacy(context, thisRef, token);
return new MediaControllerImplLegacy(context, this, token, applicationLooper);
} else {
return new MediaControllerImplBase(context, thisRef, token, connectionHints);
return new MediaControllerImplBase(context, this, token, connectionHints, applicationLooper);
}
}
@ -1805,7 +1808,7 @@ public class MediaController implements Player {
interface MediaControllerImpl {
void connect();
void connect(@UnderInitialization MediaControllerImpl this);
void addListener(Player.Listener listener);

View File

@ -154,6 +154,7 @@ import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
import org.checkerframework.checker.nullness.qual.NonNull;
@SuppressWarnings("FutureReturnValueIgnored") // TODO(b/138091975): Not to ignore if feasible
@ -161,7 +162,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
public static final String TAG = "MCImplBase";
protected final MediaController instance;
private final MediaController instance;
protected final SequencedFutureManager sequencedFutureManager;
protected final MediaControllerStub controllerStub;
@ -192,7 +193,11 @@ import org.checkerframework.checker.nullness.qual.NonNull;
private long lastSetPlayWhenReadyCalledTimeMs;
public MediaControllerImplBase(
Context context, MediaController instance, SessionToken token, Bundle connectionHints) {
Context context,
@UnderInitialization MediaController instance,
SessionToken token,
Bundle connectionHints,
Looper applicationLooper) {
// Initialize default values.
playerInfo = PlayerInfo.DEFAULT;
sessionCommands = SessionCommands.EMPTY;
@ -201,9 +206,9 @@ import org.checkerframework.checker.nullness.qual.NonNull;
intersectedPlayerCommands = Commands.EMPTY;
listeners =
new ListenerSet<>(
instance.getApplicationLooper(),
applicationLooper,
Clock.DEFAULT,
(listener, flags) -> listener.onEvents(instance, new Events(flags)));
(listener, flags) -> listener.onEvents(getInstance(), new Events(flags)));
// Initialize members
this.instance = instance;
@ -216,21 +221,26 @@ import org.checkerframework.checker.nullness.qual.NonNull;
this.connectionHints = connectionHints;
deathRecipient =
() ->
MediaControllerImplBase.this.instance.runOnApplicationLooper(
MediaControllerImplBase.this.instance::release);
MediaControllerImplBase.this
.getInstance()
.runOnApplicationLooper(MediaControllerImplBase.this.getInstance()::release);
surfaceCallback = new SurfaceCallback();
serviceConnection =
(this.token.getType() == TYPE_SESSION)
? null
: new SessionServiceConnection(connectionHints);
flushCommandQueueHandler = new FlushCommandQueueHandler(instance.getApplicationLooper());
flushCommandQueueHandler = new FlushCommandQueueHandler(applicationLooper);
lastReturnedContentPositionMs = C.TIME_UNSET;
lastSetPlayWhenReadyCalledTimeMs = C.TIME_UNSET;
}
/* package*/ MediaController getInstance() {
return instance;
}
@Override
public void connect() {
public void connect(@UnderInitialization MediaControllerImplBase this) {
boolean connectionRequested;
if (this.token.getType() == TYPE_SESSION) {
// Session
@ -241,7 +251,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
connectionRequested = requestConnectToService();
}
if (!connectionRequested) {
this.instance.runOnApplicationLooper(MediaControllerImplBase.this.instance::release);
getInstance().runOnApplicationLooper(getInstance()::release);
}
}
@ -640,8 +650,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
return playerInfo.sessionPositionInfo.positionInfo.positionMs;
}
long elapsedTimeMs =
(instance.getTimeDiffMs() != C.TIME_UNSET)
? instance.getTimeDiffMs()
(getInstance().getTimeDiffMs() != C.TIME_UNSET)
? getInstance().getTimeDiffMs()
: SystemClock.elapsedRealtime() - playerInfo.sessionPositionInfo.eventTimeMs;
long estimatedPositionMs =
playerInfo.sessionPositionInfo.positionInfo.positionMs
@ -694,8 +704,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
}
long elapsedTimeMs =
(instance.getTimeDiffMs() != C.TIME_UNSET)
? instance.getTimeDiffMs()
(getInstance().getTimeDiffMs() != C.TIME_UNSET)
? getInstance().getTimeDiffMs()
: SystemClock.elapsedRealtime() - playerInfo.sessionPositionInfo.eventTimeMs;
long estimatedPositionMs =
playerInfo.sessionPositionInfo.positionInfo.contentPositionMs
@ -2289,7 +2299,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
TAG,
"Cannot be notified about the connection result many times."
+ " Probably a bug or malicious app.");
instance.release();
getInstance().release();
return;
}
iSession = result.sessionBinder;
@ -2304,7 +2314,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
// so can be used without worrying about deadlock.
result.sessionBinder.asBinder().linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
instance.release();
getInstance().release();
return;
}
connectedToken =
@ -2315,7 +2325,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
token.getPackageName(),
result.sessionBinder,
result.tokenExtras);
instance.notifyAccepted();
getInstance().notifyAccepted();
}
private void sendControllerResult(int seq, SessionResult result) {
@ -2350,14 +2360,15 @@ import org.checkerframework.checker.nullness.qual.NonNull;
if (!isConnected()) {
return;
}
instance.notifyControllerListener(
listener -> {
ListenableFuture<SessionResult> future =
checkNotNull(
listener.onCustomCommand(instance, command, args),
"ControllerCallback#onCustomCommand() must not return null");
sendControllerResultWhenReady(seq, future);
});
getInstance()
.notifyControllerListener(
listener -> {
ListenableFuture<SessionResult> future =
checkNotNull(
listener.onCustomCommand(getInstance(), command, args),
"ControllerCallback#onCustomCommand() must not return null");
sendControllerResultWhenReady(seq, future);
});
}
@SuppressWarnings("deprecation") // Implementing and calling deprecated listener method.
@ -2558,8 +2569,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
listener -> listener.onAvailableCommandsChanged(intersectedPlayerCommands));
}
if (sessionCommandsChanged) {
instance.notifyControllerListener(
listener -> listener.onAvailableSessionCommandsChanged(instance, sessionCommands));
getInstance()
.notifyControllerListener(
listener ->
listener.onAvailableSessionCommandsChanged(getInstance(), sessionCommands));
}
}
@ -2596,21 +2609,23 @@ import org.checkerframework.checker.nullness.qual.NonNull;
validatedCustomLayout.add(button);
}
}
instance.notifyControllerListener(
listener -> {
ListenableFuture<SessionResult> future =
checkNotNull(
listener.onSetCustomLayout(instance, validatedCustomLayout),
"MediaController.Listener#onSetCustomLayout() must not return null");
sendControllerResultWhenReady(seq, future);
});
getInstance()
.notifyControllerListener(
listener -> {
ListenableFuture<SessionResult> future =
checkNotNull(
listener.onSetCustomLayout(getInstance(), validatedCustomLayout),
"MediaController.Listener#onSetCustomLayout() must not return null");
sendControllerResultWhenReady(seq, future);
});
}
public void onExtrasChanged(Bundle extras) {
if (!isConnected()) {
return;
}
instance.notifyControllerListener(listener -> listener.onExtrasChanged(instance, extras));
getInstance()
.notifyControllerListener(listener -> listener.onExtrasChanged(getInstance(), extras));
}
public void onRenderedFirstFrame() {
@ -2961,7 +2976,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
Log.w(TAG, "Service " + name + " has died prematurely");
} finally {
if (!connectionRequested) {
instance.runOnApplicationLooper(instance::release);
getInstance().runOnApplicationLooper(getInstance()::release);
}
}
}
@ -2972,7 +2987,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
// rebind, but we'd better to release() here. Otherwise ControllerCallback#onConnected()
// would be called multiple times, and the controller would be connected to the
// different session everytime.
instance.runOnApplicationLooper(instance::release);
getInstance().runOnApplicationLooper(getInstance()::release);
}
@Override
@ -2980,7 +2995,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
// Permanent lose of the binding because of the service package update or removed.
// This SessionServiceRecord will be removed accordingly, but forget session binder here
// for sure.
instance.runOnApplicationLooper(instance::release);
getInstance().runOnApplicationLooper(getInstance()::release);
}
}

View File

@ -46,6 +46,7 @@ import android.content.Context;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.support.v4.media.MediaBrowserCompat;
@ -98,6 +99,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Future;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/* package */ class MediaControllerImplLegacy implements MediaController.MediaControllerImpl {
@ -110,7 +112,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
new CueGroup(ImmutableList.of(), /* presentationTimeUs= */ 0);
/* package */ final Context context;
/* package */ final MediaController instance;
private final MediaController instance;
private final SessionToken token;
private final ListenerSet<Listener> listeners;
@ -124,26 +126,34 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private LegacyPlayerInfo pendingLegacyPlayerInfo;
private ControllerInfo controllerInfo;
public MediaControllerImplLegacy(Context context, MediaController instance, SessionToken token) {
public MediaControllerImplLegacy(
Context context,
@UnderInitialization MediaController instance,
SessionToken token,
Looper applicationLooper) {
// Initialize default values.
legacyPlayerInfo = new LegacyPlayerInfo();
pendingLegacyPlayerInfo = new LegacyPlayerInfo();
controllerInfo = new ControllerInfo();
listeners =
new ListenerSet<>(
instance.getApplicationLooper(),
applicationLooper,
Clock.DEFAULT,
(listener, flags) -> listener.onEvents(instance, new Events(flags)));
(listener, flags) -> listener.onEvents(getInstance(), new Events(flags)));
// Initialize members.
this.context = context;
this.instance = instance;
controllerCompatCallback = new ControllerCompatCallback();
controllerCompatCallback = new ControllerCompatCallback(applicationLooper);
this.token = token;
}
/* package */ MediaController getInstance() {
return instance;
}
@Override
public void connect() {
public void connect(@UnderInitialization MediaControllerImplLegacy this) {
if (this.token.getType() == SessionToken.TYPE_SESSION) {
connectToSession((MediaSessionCompat.Token) checkStateNotNull(this.token.getBinder()));
} else {
@ -590,7 +600,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
SettableFuture<SessionResult> result = SettableFuture.create();
ResultReceiver cb =
new ResultReceiver(instance.applicationHandler) {
new ResultReceiver(getInstance().applicationHandler) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
result.set(
@ -1233,36 +1243,43 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
private void connectToSession(MediaSessionCompat.Token sessionCompatToken) {
instance.runOnApplicationLooper(
() -> {
controllerCompat = new MediaControllerCompat(context, sessionCompatToken);
// Note: registerCallback() will invoke MediaControllerCompat.Callback#onSessionReady()
// if the controller is already ready.
controllerCompat.registerCallback(controllerCompatCallback, instance.applicationHandler);
});
getInstance()
.runOnApplicationLooper(
() -> {
controllerCompat = new MediaControllerCompat(context, sessionCompatToken);
// Note: registerCallback() will invoke
// MediaControllerCompat.Callback#onSessionReady()
// if the controller is already ready.
controllerCompat.registerCallback(
controllerCompatCallback, getInstance().applicationHandler);
});
// Post a runnable to prevent callbacks from being called by onConnectedNotLocked()
// before the constructor returns (b/196941334).
instance.applicationHandler.post(
() -> {
if (!controllerCompat.isSessionReady()) {
// If the session not ready here, then call onConnectedNotLocked() immediately. The
// session may be a framework MediaSession and we cannot know whether it can be ready
// later.
onConnectedNotLocked();
}
});
getInstance()
.applicationHandler
.post(
() -> {
if (!controllerCompat.isSessionReady()) {
// If the session not ready here, then call onConnectedNotLocked() immediately. The
// session may be a framework MediaSession and we cannot know whether it can be
// ready
// later.
onConnectedNotLocked();
}
});
}
private void connectToService() {
instance.runOnApplicationLooper(
() -> {
// BrowserCompat can only be used on the thread that it's created.
// Create it on the application looper to respect that.
browserCompat =
new MediaBrowserCompat(
context, token.getComponentName(), new ConnectionCallback(), null);
browserCompat.connect();
});
getInstance()
.runOnApplicationLooper(
() -> {
// BrowserCompat can only be used on the thread that it's created.
// Create it on the application looper to respect that.
browserCompat =
new MediaBrowserCompat(
context, token.getComponentName(), new ConnectionCallback(), null);
browserCompat.connect();
});
}
private boolean isPrepared() {
@ -1365,14 +1382,14 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
controllerCompat.getFlags(),
controllerCompat.isSessionReady(),
controllerCompat.getRatingType(),
instance.getTimeDiffMs());
getInstance().getTimeDiffMs());
Pair<@NullableType Integer, @NullableType Integer> reasons =
calculateDiscontinuityAndTransitionReason(
legacyPlayerInfo,
controllerInfo,
newLegacyPlayerInfo,
newControllerInfo,
instance.getTimeDiffMs());
getInstance().getTimeDiffMs());
updateControllerInfo(
notifyConnected,
newLegacyPlayerInfo,
@ -1412,11 +1429,13 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
controllerInfo = newControllerInfo;
if (notifyConnected) {
instance.notifyAccepted();
getInstance().notifyAccepted();
if (!oldControllerInfo.customLayout.equals(newControllerInfo.customLayout)) {
instance.notifyControllerListener(
listener ->
ignoreFuture(listener.onSetCustomLayout(instance, newControllerInfo.customLayout)));
getInstance()
.notifyControllerListener(
listener ->
ignoreFuture(
listener.onSetCustomLayout(getInstance(), newControllerInfo.customLayout)));
}
return;
}
@ -1537,15 +1556,18 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
if (!oldControllerInfo.availableSessionCommands.equals(
newControllerInfo.availableSessionCommands)) {
instance.notifyControllerListener(
listener ->
listener.onAvailableSessionCommandsChanged(
instance, newControllerInfo.availableSessionCommands));
getInstance()
.notifyControllerListener(
listener ->
listener.onAvailableSessionCommandsChanged(
getInstance(), newControllerInfo.availableSessionCommands));
}
if (!oldControllerInfo.customLayout.equals(newControllerInfo.customLayout)) {
instance.notifyControllerListener(
listener ->
ignoreFuture(listener.onSetCustomLayout(instance, newControllerInfo.customLayout)));
getInstance()
.notifyControllerListener(
listener ->
ignoreFuture(
listener.onSetCustomLayout(getInstance(), newControllerInfo.customLayout)));
}
listeners.flushEvents();
}
@ -1566,12 +1588,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Override
public void onConnectionSuspended() {
instance.release();
getInstance().release();
}
@Override
public void onConnectionFailed() {
instance.release();
getInstance().release();
}
}
@ -1581,10 +1603,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private final Handler pendingChangesHandler;
public ControllerCompatCallback() {
public ControllerCompatCallback(Looper applicationLooper) {
pendingChangesHandler =
new Handler(
instance.applicationHandler.getLooper(),
applicationLooper,
(msg) -> {
if (msg.what == MSG_HANDLE_PENDING_UPDATES) {
handleNewLegacyParameters(/* notifyConnected= */ false, pendingLegacyPlayerInfo);
@ -1620,16 +1642,19 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Override
public void onSessionDestroyed() {
instance.release();
getInstance().release();
}
@Override
public void onSessionEvent(String event, Bundle extras) {
instance.notifyControllerListener(
listener ->
ignoreFuture(
listener.onCustomCommand(
instance, new SessionCommand(event, /* extras= */ Bundle.EMPTY), extras)));
getInstance()
.notifyControllerListener(
listener ->
ignoreFuture(
listener.onCustomCommand(
getInstance(),
new SessionCommand(event, /* extras= */ Bundle.EMPTY),
extras)));
}
@Override
@ -1661,7 +1686,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Override
public void onExtrasChanged(Bundle extras) {
instance.notifyControllerListener(listener -> listener.onExtrasChanged(instance, extras));
getInstance()
.notifyControllerListener(listener -> listener.onExtrasChanged(getInstance(), extras));
}
@Override
@ -1672,17 +1698,19 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Override
public void onCaptioningEnabledChanged(boolean enabled) {
instance.notifyControllerListener(
listener -> {
Bundle args = new Bundle();
args.putBoolean(ARGUMENT_CAPTIONING_ENABLED, enabled);
ignoreFuture(
listener.onCustomCommand(
instance,
new SessionCommand(
SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED, /* extras= */ Bundle.EMPTY),
args));
});
getInstance()
.notifyControllerListener(
listener -> {
Bundle args = new Bundle();
args.putBoolean(ARGUMENT_CAPTIONING_ENABLED, enabled);
ignoreFuture(
listener.onCustomCommand(
getInstance(),
new SessionCommand(
SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED,
/* extras= */ Bundle.EMPTY),
args));
});
}
@Override

View File

@ -87,7 +87,8 @@ import java.util.List;
@Override
public void onDisconnected(int seq) {
dispatchControllerTaskOnHandler(
controller -> controller.instance.runOnApplicationLooper(controller.instance::release));
controller ->
controller.getInstance().runOnApplicationLooper(controller.getInstance()::release));
}
@Override
@ -268,7 +269,7 @@ import java.util.List;
if (controller == null) {
return;
}
Handler handler = controller.instance.applicationHandler;
Handler handler = controller.getInstance().applicationHandler;
postOrRun(
handler,
() -> {