Commit 287c7579 by ibaker Committed by Rohit Singh

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
parent 2f977eee
...@@ -38,6 +38,9 @@ import com.google.common.collect.ImmutableList; ...@@ -38,6 +38,9 @@ import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.Executor; 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 * Browses media content offered by a {@link MediaLibraryService} in addition to the {@link
...@@ -45,12 +48,6 @@ import java.util.concurrent.Executor; ...@@ -45,12 +48,6 @@ import java.util.concurrent.Executor;
*/ */
public final class MediaBrowser extends MediaController { 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}. */ /** A builder for {@link MediaBrowser}. */
public static final class Builder { public static final class Builder {
...@@ -201,6 +198,12 @@ public final class MediaBrowser extends MediaController { ...@@ -201,6 +198,12 @@ public final class MediaBrowser extends MediaController {
@Nullable LibraryParams params) {} @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}. */ /** Creates an instance from the {@link SessionToken}. */
/* package */ MediaBrowser( /* package */ MediaBrowser(
Context context, Context context,
...@@ -210,17 +213,24 @@ public final class MediaBrowser extends MediaController { ...@@ -210,17 +213,24 @@ public final class MediaBrowser extends MediaController {
Looper applicationLooper, Looper applicationLooper,
ConnectionCallback connectionCallback) { ConnectionCallback connectionCallback) {
super(context, token, connectionHints, listener, applicationLooper, connectionCallback); super(context, token, connectionHints, listener, applicationLooper, connectionCallback);
this.impl = (MediaBrowserImpl) super.impl;
} }
@Override @Override
/* package */ MediaBrowserImpl createImpl( /* package */ @UnderInitialization
Context context, MediaController thisRef, SessionToken token, Bundle connectionHints) { MediaBrowserImpl createImpl(
@UnderInitialization MediaBrowser this,
Context context,
SessionToken token,
Bundle connectionHints,
Looper applicationLooper) {
MediaBrowserImpl impl;
if (token.isLegacySession()) { if (token.isLegacySession()) {
return new MediaBrowserImplLegacy(context, (MediaBrowser) thisRef, token); impl = new MediaBrowserImplLegacy(context, this, token, applicationLooper);
} else { } 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 { ...@@ -234,7 +244,7 @@ public final class MediaBrowser extends MediaController {
public ListenableFuture<LibraryResult<MediaItem>> getLibraryRoot(@Nullable LibraryParams params) { public ListenableFuture<LibraryResult<MediaItem>> getLibraryRoot(@Nullable LibraryParams params) {
verifyApplicationThread(); verifyApplicationThread();
if (isConnected()) { if (isConnected()) {
return impl.getLibraryRoot(params); return checkNotNull(impl).getLibraryRoot(params);
} }
return createDisconnectedFuture(); return createDisconnectedFuture();
} }
...@@ -254,7 +264,7 @@ public final class MediaBrowser extends MediaController { ...@@ -254,7 +264,7 @@ public final class MediaBrowser extends MediaController {
verifyApplicationThread(); verifyApplicationThread();
checkNotEmpty(parentId, "parentId must not be empty"); checkNotEmpty(parentId, "parentId must not be empty");
if (isConnected()) { if (isConnected()) {
return impl.subscribe(parentId, params); return checkNotNull(impl).subscribe(parentId, params);
} }
return createDisconnectedFuture(); return createDisconnectedFuture();
} }
...@@ -273,7 +283,7 @@ public final class MediaBrowser extends MediaController { ...@@ -273,7 +283,7 @@ public final class MediaBrowser extends MediaController {
verifyApplicationThread(); verifyApplicationThread();
checkNotEmpty(parentId, "parentId must not be empty"); checkNotEmpty(parentId, "parentId must not be empty");
if (isConnected()) { if (isConnected()) {
return impl.unsubscribe(parentId); return checkNotNull(impl).unsubscribe(parentId);
} }
return createDisconnectedFuture(); return createDisconnectedFuture();
} }
...@@ -299,7 +309,7 @@ public final class MediaBrowser extends MediaController { ...@@ -299,7 +309,7 @@ public final class MediaBrowser extends MediaController {
checkArgument(page >= 0, "page must not be negative"); checkArgument(page >= 0, "page must not be negative");
checkArgument(pageSize >= 1, "pageSize must not be less than 1"); checkArgument(pageSize >= 1, "pageSize must not be less than 1");
if (isConnected()) { if (isConnected()) {
return impl.getChildren(parentId, page, pageSize, params); return checkNotNull(impl).getChildren(parentId, page, pageSize, params);
} }
return createDisconnectedFuture(); return createDisconnectedFuture();
} }
...@@ -316,7 +326,7 @@ public final class MediaBrowser extends MediaController { ...@@ -316,7 +326,7 @@ public final class MediaBrowser extends MediaController {
verifyApplicationThread(); verifyApplicationThread();
checkNotEmpty(mediaId, "mediaId must not be empty"); checkNotEmpty(mediaId, "mediaId must not be empty");
if (isConnected()) { if (isConnected()) {
return impl.getItem(mediaId); return checkNotNull(impl).getItem(mediaId);
} }
return createDisconnectedFuture(); return createDisconnectedFuture();
} }
...@@ -338,7 +348,7 @@ public final class MediaBrowser extends MediaController { ...@@ -338,7 +348,7 @@ public final class MediaBrowser extends MediaController {
verifyApplicationThread(); verifyApplicationThread();
checkNotEmpty(query, "query must not be empty"); checkNotEmpty(query, "query must not be empty");
if (isConnected()) { if (isConnected()) {
return impl.search(query, params); return checkNotNull(impl).search(query, params);
} }
return createDisconnectedFuture(); return createDisconnectedFuture();
} }
...@@ -365,7 +375,7 @@ public final class MediaBrowser extends MediaController { ...@@ -365,7 +375,7 @@ public final class MediaBrowser extends MediaController {
checkArgument(page >= 0, "page must not be negative"); checkArgument(page >= 0, "page must not be negative");
checkArgument(pageSize >= 1, "pageSize must not be less than 1"); checkArgument(pageSize >= 1, "pageSize must not be less than 1");
if (isConnected()) { if (isConnected()) {
return impl.getSearchResult(query, page, pageSize, params); return checkNotNull(impl).getSearchResult(query, page, pageSize, params);
} }
return createDisconnectedFuture(); return createDisconnectedFuture();
} }
......
...@@ -28,6 +28,7 @@ import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_UNSUBS ...@@ -28,6 +28,7 @@ import static androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_UNSUBS
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.Looper;
import android.os.RemoteException; import android.os.RemoteException;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
...@@ -37,18 +38,27 @@ import androidx.media3.session.SequencedFutureManager.SequencedFuture; ...@@ -37,18 +38,27 @@ import androidx.media3.session.SequencedFutureManager.SequencedFuture;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
/** Base implementation of MediaBrowser. */ /** Base implementation of MediaBrowser. */
/* package */ class MediaBrowserImplBase extends MediaControllerImplBase /* package */ class MediaBrowserImplBase extends MediaControllerImplBase
implements MediaBrowser.MediaBrowserImpl { implements MediaBrowser.MediaBrowserImpl {
private final MediaBrowser instance;
MediaBrowserImplBase( MediaBrowserImplBase(
Context context, MediaController instance, SessionToken token, Bundle connectionHints) { Context context,
super(context, instance, token, connectionHints); @UnderInitialization MediaBrowser instance,
SessionToken token,
Bundle connectionHints,
Looper applicationLooper) {
super(context, instance, token, connectionHints, applicationLooper);
this.instance = instance;
} }
MediaBrowser getMediaBrowser() { @Override
return (MediaBrowser) instance; /* package */ MediaBrowser getInstance() {
return instance;
} }
@Override @Override
...@@ -157,10 +167,10 @@ import com.google.common.util.concurrent.ListenableFuture; ...@@ -157,10 +167,10 @@ import com.google.common.util.concurrent.ListenableFuture;
if (!isConnected()) { if (!isConnected()) {
return; return;
} }
getMediaBrowser() getInstance()
.notifyBrowserListener( .notifyBrowserListener(
listener -> listener ->
listener.onSearchResultChanged(getMediaBrowser(), query, itemCount, libraryParams)); listener.onSearchResultChanged(getInstance(), query, itemCount, libraryParams));
} }
void notifyChildrenChanged( void notifyChildrenChanged(
...@@ -168,10 +178,10 @@ import com.google.common.util.concurrent.ListenableFuture; ...@@ -168,10 +178,10 @@ import com.google.common.util.concurrent.ListenableFuture;
if (!isConnected()) { if (!isConnected()) {
return; return;
} }
getMediaBrowser() getInstance()
.notifyBrowserListener( .notifyBrowserListener(
listener -> listener ->
listener.onChildrenChanged(getMediaBrowser(), parentId, itemCount, libraryParams)); listener.onChildrenChanged(getInstance(), parentId, itemCount, libraryParams));
} }
private <V> ListenableFuture<LibraryResult<V>> dispatchRemoteLibrarySessionTask( private <V> ListenableFuture<LibraryResult<V>> dispatchRemoteLibrarySessionTask(
......
...@@ -22,6 +22,7 @@ import static androidx.media3.session.LibraryResult.RESULT_ERROR_UNKNOWN; ...@@ -22,6 +22,7 @@ import static androidx.media3.session.LibraryResult.RESULT_ERROR_UNKNOWN;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.Looper;
import android.support.v4.media.MediaBrowserCompat; import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaBrowserCompat.ItemCallback; import android.support.v4.media.MediaBrowserCompat.ItemCallback;
import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback; import android.support.v4.media.MediaBrowserCompat.SubscriptionCallback;
...@@ -38,6 +39,7 @@ import com.google.common.util.concurrent.SettableFuture; ...@@ -38,6 +39,7 @@ import com.google.common.util.concurrent.SettableFuture;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
/** Implementation of MediaBrowser with the {@link MediaBrowserCompat} for legacy support. */ /** Implementation of MediaBrowser with the {@link MediaBrowserCompat} for legacy support. */
/* package */ class MediaBrowserImplLegacy extends MediaControllerImplLegacy /* package */ class MediaBrowserImplLegacy extends MediaControllerImplLegacy
...@@ -49,12 +51,20 @@ import java.util.List; ...@@ -49,12 +51,20 @@ import java.util.List;
private final HashMap<String, List<SubscribeCallback>> subscribeCallbacks = new HashMap<>(); private final HashMap<String, List<SubscribeCallback>> subscribeCallbacks = new HashMap<>();
MediaBrowserImplLegacy(Context context, MediaBrowser instance, SessionToken token) { private final MediaBrowser instance;
super(context, instance, token);
MediaBrowserImplLegacy(
Context context,
@UnderInitialization MediaBrowser instance,
SessionToken token,
Looper applicationLooper) {
super(context, instance, token, applicationLooper);
this.instance = instance;
} }
MediaBrowser getMediaBrowser() { @Override
return (MediaBrowser) instance; /* package*/ MediaBrowser getInstance() {
return instance;
} }
@Override @Override
...@@ -78,7 +88,8 @@ import java.util.List; ...@@ -78,7 +88,8 @@ import java.util.List;
@Override @Override
public ListenableFuture<LibraryResult<MediaItem>> getLibraryRoot(@Nullable LibraryParams params) { 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)); return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED));
} }
SettableFuture<LibraryResult<MediaItem>> result = SettableFuture.create(); SettableFuture<LibraryResult<MediaItem>> result = SettableFuture.create();
...@@ -103,7 +114,7 @@ import java.util.List; ...@@ -103,7 +114,7 @@ import java.util.List;
@Override @Override
public ListenableFuture<LibraryResult<Void>> subscribe( public ListenableFuture<LibraryResult<Void>> subscribe(
String parentId, @Nullable LibraryParams params) { 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)); return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED));
} }
MediaBrowserCompat browserCompat = getBrowserCompat(); MediaBrowserCompat browserCompat = getBrowserCompat();
...@@ -124,7 +135,7 @@ import java.util.List; ...@@ -124,7 +135,7 @@ import java.util.List;
@Override @Override
public ListenableFuture<LibraryResult<Void>> unsubscribe(String parentId) { 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)); return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED));
} }
MediaBrowserCompat browserCompat = getBrowserCompat(); MediaBrowserCompat browserCompat = getBrowserCompat();
...@@ -148,7 +159,8 @@ import java.util.List; ...@@ -148,7 +159,8 @@ import java.util.List;
@Override @Override
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getChildren( public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getChildren(
String parentId, int page, int pageSize, @Nullable LibraryParams params) { 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)); return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED));
} }
MediaBrowserCompat browserCompat = getBrowserCompat(); MediaBrowserCompat browserCompat = getBrowserCompat();
...@@ -164,7 +176,7 @@ import java.util.List; ...@@ -164,7 +176,7 @@ import java.util.List;
@Override @Override
public ListenableFuture<LibraryResult<MediaItem>> getItem(String mediaId) { 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)); return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED));
} }
MediaBrowserCompat browserCompat = getBrowserCompat(); MediaBrowserCompat browserCompat = getBrowserCompat();
...@@ -196,7 +208,7 @@ import java.util.List; ...@@ -196,7 +208,7 @@ import java.util.List;
@Override @Override
public ListenableFuture<LibraryResult<Void>> search( public ListenableFuture<LibraryResult<Void>> search(
String query, @Nullable LibraryParams params) { 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)); return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED));
} }
MediaBrowserCompat browserCompat = getBrowserCompat(); MediaBrowserCompat browserCompat = getBrowserCompat();
...@@ -210,7 +222,7 @@ import java.util.List; ...@@ -210,7 +222,7 @@ import java.util.List;
@Override @Override
public void onSearchResult( public void onSearchResult(
String query, Bundle extras, List<MediaBrowserCompat.MediaItem> items) { String query, Bundle extras, List<MediaBrowserCompat.MediaItem> items) {
getMediaBrowser() getInstance()
.notifyBrowserListener( .notifyBrowserListener(
listener -> { listener -> {
// Set extra null here, because 'extra' have different meanings between old // Set extra null here, because 'extra' have different meanings between old
...@@ -218,20 +230,20 @@ import java.util.List; ...@@ -218,20 +230,20 @@ import java.util.List;
// - Old API: Extra/Option specified with search(). // - Old API: Extra/Option specified with search().
// - New API: Extra from MediaLibraryService to MediaBrowser // - New API: Extra from MediaLibraryService to MediaBrowser
// TODO(b/193193565): Cache search result for later getSearchResult() calls. // 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 @Override
public void onError(String query, Bundle extras) { public void onError(String query, Bundle extras) {
getMediaBrowser() getInstance()
.notifyBrowserListener( .notifyBrowserListener(
listener -> { listener -> {
// Set extra null here, because 'extra' have different meanings between old // Set extra null here, because 'extra' have different meanings between old
// API and new API as follows. // API and new API as follows.
// - Old API: Extra/Option specified with search(). // - Old API: Extra/Option specified with search().
// - New API: Extra from MediaLibraryService to MediaBrowser // - 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; ...@@ -242,8 +254,8 @@ import java.util.List;
@Override @Override
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getSearchResult( public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getSearchResult(
String query, int page, int pageSize, @Nullable LibraryParams params) { String query, int page, int pageSize, @Nullable LibraryParams params) {
if (!instance.isSessionCommandAvailable( if (!getInstance()
SessionCommand.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT)) { .isSessionCommandAvailable(SessionCommand.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT)) {
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED)); return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_PERMISSION_DENIED));
} }
MediaBrowserCompat browserCompat = getBrowserCompat(); MediaBrowserCompat browserCompat = getBrowserCompat();
...@@ -401,11 +413,11 @@ import java.util.List; ...@@ -401,11 +413,11 @@ import java.util.List;
LibraryParams params = LibraryParams params =
MediaUtils.convertToLibraryParams( MediaUtils.convertToLibraryParams(
context, browserCompat.getNotifyChildrenChangedOptions()); context, browserCompat.getNotifyChildrenChangedOptions());
getMediaBrowser() getInstance()
.notifyBrowserListener( .notifyBrowserListener(
listener -> { listener -> {
// TODO(b/193193565): Cache children result for later getChildren() calls. // 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()); future.set(LibraryResult.ofVoid());
} }
......
...@@ -64,7 +64,8 @@ import java.util.concurrent.CancellationException; ...@@ -64,7 +64,8 @@ import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Future; 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 * A controller that interacts with a {@link MediaSession}, a {@link MediaSessionService} hosting a
...@@ -375,7 +376,7 @@ public class MediaController implements Player { ...@@ -375,7 +376,7 @@ public class MediaController implements Player {
private boolean released; private boolean released;
/* package */ final MediaControllerImpl impl; @NotOnlyInitialized private final MediaControllerImpl impl;
/* package */ final Listener listener; /* package */ final Listener listener;
...@@ -407,19 +408,21 @@ public class MediaController implements Player { ...@@ -407,19 +408,21 @@ public class MediaController implements Player {
applicationHandler = new Handler(applicationLooper); applicationHandler = new Handler(applicationLooper);
this.connectionCallback = connectionCallback; this.connectionCallback = connectionCallback;
@SuppressWarnings("nullness:assignment") impl = createImpl(context, token, connectionHints, applicationLooper);
@Initialized
MediaController thisRef = this;
impl = thisRef.createImpl(context, thisRef, token, connectionHints);
impl.connect(); impl.connect();
} }
/* package */ MediaControllerImpl createImpl( /* package */ @UnderInitialization
Context context, MediaController thisRef, SessionToken token, Bundle connectionHints) { MediaControllerImpl createImpl(
@UnderInitialization MediaController this,
Context context,
SessionToken token,
Bundle connectionHints,
Looper applicationLooper) {
if (token.isLegacySession()) { if (token.isLegacySession()) {
return new MediaControllerImplLegacy(context, thisRef, token); return new MediaControllerImplLegacy(context, this, token, applicationLooper);
} else { } 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 { ...@@ -1805,7 +1808,7 @@ public class MediaController implements Player {
interface MediaControllerImpl { interface MediaControllerImpl {
void connect(); void connect(@UnderInitialization MediaControllerImpl this);
void addListener(Player.Listener listener); void addListener(Player.Listener listener);
......
...@@ -154,6 +154,7 @@ import java.util.concurrent.CancellationException; ...@@ -154,6 +154,7 @@ import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
@SuppressWarnings("FutureReturnValueIgnored") // TODO(b/138091975): Not to ignore if feasible @SuppressWarnings("FutureReturnValueIgnored") // TODO(b/138091975): Not to ignore if feasible
...@@ -161,7 +162,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -161,7 +162,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
public static final String TAG = "MCImplBase"; public static final String TAG = "MCImplBase";
protected final MediaController instance; private final MediaController instance;
protected final SequencedFutureManager sequencedFutureManager; protected final SequencedFutureManager sequencedFutureManager;
protected final MediaControllerStub controllerStub; protected final MediaControllerStub controllerStub;
...@@ -192,7 +193,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -192,7 +193,11 @@ import org.checkerframework.checker.nullness.qual.NonNull;
private long lastSetPlayWhenReadyCalledTimeMs; private long lastSetPlayWhenReadyCalledTimeMs;
public MediaControllerImplBase( public MediaControllerImplBase(
Context context, MediaController instance, SessionToken token, Bundle connectionHints) { Context context,
@UnderInitialization MediaController instance,
SessionToken token,
Bundle connectionHints,
Looper applicationLooper) {
// Initialize default values. // Initialize default values.
playerInfo = PlayerInfo.DEFAULT; playerInfo = PlayerInfo.DEFAULT;
sessionCommands = SessionCommands.EMPTY; sessionCommands = SessionCommands.EMPTY;
...@@ -201,9 +206,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -201,9 +206,9 @@ import org.checkerframework.checker.nullness.qual.NonNull;
intersectedPlayerCommands = Commands.EMPTY; intersectedPlayerCommands = Commands.EMPTY;
listeners = listeners =
new ListenerSet<>( new ListenerSet<>(
instance.getApplicationLooper(), applicationLooper,
Clock.DEFAULT, Clock.DEFAULT,
(listener, flags) -> listener.onEvents(instance, new Events(flags))); (listener, flags) -> listener.onEvents(getInstance(), new Events(flags)));
// Initialize members // Initialize members
this.instance = instance; this.instance = instance;
...@@ -216,21 +221,26 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -216,21 +221,26 @@ import org.checkerframework.checker.nullness.qual.NonNull;
this.connectionHints = connectionHints; this.connectionHints = connectionHints;
deathRecipient = deathRecipient =
() -> () ->
MediaControllerImplBase.this.instance.runOnApplicationLooper( MediaControllerImplBase.this
MediaControllerImplBase.this.instance::release); .getInstance()
.runOnApplicationLooper(MediaControllerImplBase.this.getInstance()::release);
surfaceCallback = new SurfaceCallback(); surfaceCallback = new SurfaceCallback();
serviceConnection = serviceConnection =
(this.token.getType() == TYPE_SESSION) (this.token.getType() == TYPE_SESSION)
? null ? null
: new SessionServiceConnection(connectionHints); : new SessionServiceConnection(connectionHints);
flushCommandQueueHandler = new FlushCommandQueueHandler(instance.getApplicationLooper()); flushCommandQueueHandler = new FlushCommandQueueHandler(applicationLooper);
lastReturnedContentPositionMs = C.TIME_UNSET; lastReturnedContentPositionMs = C.TIME_UNSET;
lastSetPlayWhenReadyCalledTimeMs = C.TIME_UNSET; lastSetPlayWhenReadyCalledTimeMs = C.TIME_UNSET;
} }
/* package*/ MediaController getInstance() {
return instance;
}
@Override @Override
public void connect() { public void connect(@UnderInitialization MediaControllerImplBase this) {
boolean connectionRequested; boolean connectionRequested;
if (this.token.getType() == TYPE_SESSION) { if (this.token.getType() == TYPE_SESSION) {
// Session // Session
...@@ -241,7 +251,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -241,7 +251,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
connectionRequested = requestConnectToService(); connectionRequested = requestConnectToService();
} }
if (!connectionRequested) { if (!connectionRequested) {
this.instance.runOnApplicationLooper(MediaControllerImplBase.this.instance::release); getInstance().runOnApplicationLooper(getInstance()::release);
} }
} }
...@@ -640,8 +650,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -640,8 +650,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
return playerInfo.sessionPositionInfo.positionInfo.positionMs; return playerInfo.sessionPositionInfo.positionInfo.positionMs;
} }
long elapsedTimeMs = long elapsedTimeMs =
(instance.getTimeDiffMs() != C.TIME_UNSET) (getInstance().getTimeDiffMs() != C.TIME_UNSET)
? instance.getTimeDiffMs() ? getInstance().getTimeDiffMs()
: SystemClock.elapsedRealtime() - playerInfo.sessionPositionInfo.eventTimeMs; : SystemClock.elapsedRealtime() - playerInfo.sessionPositionInfo.eventTimeMs;
long estimatedPositionMs = long estimatedPositionMs =
playerInfo.sessionPositionInfo.positionInfo.positionMs playerInfo.sessionPositionInfo.positionInfo.positionMs
...@@ -694,8 +704,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -694,8 +704,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
} }
long elapsedTimeMs = long elapsedTimeMs =
(instance.getTimeDiffMs() != C.TIME_UNSET) (getInstance().getTimeDiffMs() != C.TIME_UNSET)
? instance.getTimeDiffMs() ? getInstance().getTimeDiffMs()
: SystemClock.elapsedRealtime() - playerInfo.sessionPositionInfo.eventTimeMs; : SystemClock.elapsedRealtime() - playerInfo.sessionPositionInfo.eventTimeMs;
long estimatedPositionMs = long estimatedPositionMs =
playerInfo.sessionPositionInfo.positionInfo.contentPositionMs playerInfo.sessionPositionInfo.positionInfo.contentPositionMs
...@@ -2289,7 +2299,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -2289,7 +2299,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
TAG, TAG,
"Cannot be notified about the connection result many times." "Cannot be notified about the connection result many times."
+ " Probably a bug or malicious app."); + " Probably a bug or malicious app.");
instance.release(); getInstance().release();
return; return;
} }
iSession = result.sessionBinder; iSession = result.sessionBinder;
...@@ -2304,7 +2314,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -2304,7 +2314,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
// so can be used without worrying about deadlock. // so can be used without worrying about deadlock.
result.sessionBinder.asBinder().linkToDeath(deathRecipient, 0); result.sessionBinder.asBinder().linkToDeath(deathRecipient, 0);
} catch (RemoteException e) { } catch (RemoteException e) {
instance.release(); getInstance().release();
return; return;
} }
connectedToken = connectedToken =
...@@ -2315,7 +2325,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -2315,7 +2325,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
token.getPackageName(), token.getPackageName(),
result.sessionBinder, result.sessionBinder,
result.tokenExtras); result.tokenExtras);
instance.notifyAccepted(); getInstance().notifyAccepted();
} }
private void sendControllerResult(int seq, SessionResult result) { private void sendControllerResult(int seq, SessionResult result) {
...@@ -2350,14 +2360,15 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -2350,14 +2360,15 @@ import org.checkerframework.checker.nullness.qual.NonNull;
if (!isConnected()) { if (!isConnected()) {
return; return;
} }
instance.notifyControllerListener( getInstance()
listener -> { .notifyControllerListener(
ListenableFuture<SessionResult> future = listener -> {
checkNotNull( ListenableFuture<SessionResult> future =
listener.onCustomCommand(instance, command, args), checkNotNull(
"ControllerCallback#onCustomCommand() must not return null"); listener.onCustomCommand(getInstance(), command, args),
sendControllerResultWhenReady(seq, future); "ControllerCallback#onCustomCommand() must not return null");
}); sendControllerResultWhenReady(seq, future);
});
} }
@SuppressWarnings("deprecation") // Implementing and calling deprecated listener method. @SuppressWarnings("deprecation") // Implementing and calling deprecated listener method.
...@@ -2558,8 +2569,10 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -2558,8 +2569,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
listener -> listener.onAvailableCommandsChanged(intersectedPlayerCommands)); listener -> listener.onAvailableCommandsChanged(intersectedPlayerCommands));
} }
if (sessionCommandsChanged) { if (sessionCommandsChanged) {
instance.notifyControllerListener( getInstance()
listener -> listener.onAvailableSessionCommandsChanged(instance, sessionCommands)); .notifyControllerListener(
listener ->
listener.onAvailableSessionCommandsChanged(getInstance(), sessionCommands));
} }
} }
...@@ -2596,21 +2609,23 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -2596,21 +2609,23 @@ import org.checkerframework.checker.nullness.qual.NonNull;
validatedCustomLayout.add(button); validatedCustomLayout.add(button);
} }
} }
instance.notifyControllerListener( getInstance()
listener -> { .notifyControllerListener(
ListenableFuture<SessionResult> future = listener -> {
checkNotNull( ListenableFuture<SessionResult> future =
listener.onSetCustomLayout(instance, validatedCustomLayout), checkNotNull(
"MediaController.Listener#onSetCustomLayout() must not return null"); listener.onSetCustomLayout(getInstance(), validatedCustomLayout),
sendControllerResultWhenReady(seq, future); "MediaController.Listener#onSetCustomLayout() must not return null");
}); sendControllerResultWhenReady(seq, future);
});
} }
public void onExtrasChanged(Bundle extras) { public void onExtrasChanged(Bundle extras) {
if (!isConnected()) { if (!isConnected()) {
return; return;
} }
instance.notifyControllerListener(listener -> listener.onExtrasChanged(instance, extras)); getInstance()
.notifyControllerListener(listener -> listener.onExtrasChanged(getInstance(), extras));
} }
public void onRenderedFirstFrame() { public void onRenderedFirstFrame() {
...@@ -2961,7 +2976,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -2961,7 +2976,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
Log.w(TAG, "Service " + name + " has died prematurely"); Log.w(TAG, "Service " + name + " has died prematurely");
} finally { } finally {
if (!connectionRequested) { if (!connectionRequested) {
instance.runOnApplicationLooper(instance::release); getInstance().runOnApplicationLooper(getInstance()::release);
} }
} }
} }
...@@ -2972,7 +2987,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -2972,7 +2987,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
// rebind, but we'd better to release() here. Otherwise ControllerCallback#onConnected() // rebind, but we'd better to release() here. Otherwise ControllerCallback#onConnected()
// would be called multiple times, and the controller would be connected to the // would be called multiple times, and the controller would be connected to the
// different session everytime. // different session everytime.
instance.runOnApplicationLooper(instance::release); getInstance().runOnApplicationLooper(getInstance()::release);
} }
@Override @Override
...@@ -2980,7 +2995,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; ...@@ -2980,7 +2995,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
// Permanent lose of the binding because of the service package update or removed. // Permanent lose of the binding because of the service package update or removed.
// This SessionServiceRecord will be removed accordingly, but forget session binder here // This SessionServiceRecord will be removed accordingly, but forget session binder here
// for sure. // for sure.
instance.runOnApplicationLooper(instance::release); getInstance().runOnApplicationLooper(getInstance()::release);
} }
} }
......
...@@ -87,7 +87,8 @@ import java.util.List; ...@@ -87,7 +87,8 @@ import java.util.List;
@Override @Override
public void onDisconnected(int seq) { public void onDisconnected(int seq) {
dispatchControllerTaskOnHandler( dispatchControllerTaskOnHandler(
controller -> controller.instance.runOnApplicationLooper(controller.instance::release)); controller ->
controller.getInstance().runOnApplicationLooper(controller.getInstance()::release));
} }
@Override @Override
...@@ -268,7 +269,7 @@ import java.util.List; ...@@ -268,7 +269,7 @@ import java.util.List;
if (controller == null) { if (controller == null) {
return; return;
} }
Handler handler = controller.instance.applicationHandler; Handler handler = controller.getInstance().applicationHandler;
postOrRun( postOrRun(
handler, handler,
() -> { () -> {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment