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:
parent
06d3c07a1c
commit
0667c74dc5
@ -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();
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
() -> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user