1515 */
1616package androidx .media3 .cast ;
1717
18+ import static androidx .media3 .cast .CastUtils .verifyMainThread ;
19+ import static com .google .common .base .Preconditions .checkNotNull ;
20+
1821import android .annotation .SuppressLint ;
1922import android .content .Context ;
20- import android .os .Looper ;
2123import androidx .annotation .MainThread ;
2224import androidx .annotation .Nullable ;
2325import androidx .annotation .VisibleForTesting ;
24- import androidx .core .util .Preconditions ;
2526import androidx .media3 .common .util .BackgroundExecutor ;
2627import androidx .media3 .common .util .Log ;
2728import androidx .media3 .common .util .UnstableApi ;
29+ import androidx .mediarouter .media .MediaRouteSelector ;
2830import com .google .android .gms .cast .framework .CastContext ;
2931import com .google .android .gms .cast .framework .CastSession ;
3032import com .google .android .gms .cast .framework .SessionManager ;
3133import com .google .android .gms .cast .framework .SessionManagerListener ;
3234import com .google .android .gms .tasks .Task ;
3335import com .google .errorprone .annotations .CanIgnoreReturnValue ;
3436import java .util .ArrayList ;
37+ import java .util .HashSet ;
3538import java .util .List ;
39+ import java .util .Set ;
3640import org .checkerframework .checker .nullness .qual .MonotonicNonNull ;
3741
3842/**
@@ -54,14 +58,17 @@ public class CastContextWrapper {
5458 @ Nullable
5559 private static CastContextWrapper singletonInstance ;
5660
57- @ Nullable private CastContext castContext ;
5861 private final List <SessionManagerListener <CastSession >> pendingListeners ;
62+
63+ private final Set <MediaRouteSelectorListener > pendingMediaRouteSelectorListeners ;
64+
65+ @ Nullable private CastContext castContext ;
5966 private @ MonotonicNonNull Throwable castContextLoadFailure ;
6067 private boolean isInitOngoing ;
6168
6269 /** Returns a singleton instance of {@link CastContextWrapper}. */
6370 public static CastContextWrapper getSingletonInstance () {
64- checkRunningOnMainThread ();
71+ verifyMainThread ();
6572 if (singletonInstance == null ) {
6673 singletonInstance = new CastContextWrapper ();
6774 }
@@ -76,8 +83,8 @@ public static CastContextWrapper getSingletonInstance() {
7683 */
7784 @ CanIgnoreReturnValue
7885 public CastContextWrapper initWithContext (CastContext castContext ) {
79- checkRunningOnMainThread ();
80- Preconditions . checkNotNull (castContext );
86+ verifyMainThread ();
87+ checkNotNull (castContext );
8188 if (needsInitialization ()) {
8289 setCastContext (castContext );
8390 }
@@ -99,7 +106,7 @@ public void asyncInit(Context context) {
99106
100107 @ VisibleForTesting
101108 /* package */ void asyncInit (CastContextInitializer castContextInitializer ) {
102- checkRunningOnMainThread ();
109+ verifyMainThread ();
103110 if (!needsInitialization ()) {
104111 Log .w (TAG , "Tried to initialize an already initialized CastContextWrapper." );
105112 return ;
@@ -147,7 +154,7 @@ public Throwable getCastContextLoadFailure() {
147154 * @param listener The listener to register.
148155 */
149156 public void addSessionManagerListener (SessionManagerListener <CastSession > listener ) {
150- checkRunningOnMainThread ();
157+ verifyMainThread ();
151158 if (castContext != null ) {
152159 castContext .getSessionManager ().addSessionManagerListener (listener , CastSession .class );
153160 } else {
@@ -157,21 +164,40 @@ public void addSessionManagerListener(SessionManagerListener<CastSession> listen
157164
158165 /** Unregisters the given session manager listener. */
159166 public void removeSessionManagerListener (SessionManagerListener <CastSession > listener ) {
160- checkRunningOnMainThread ();
167+ verifyMainThread ();
161168 if (castContext != null ) {
162169 castContext .getSessionManager ().removeSessionManagerListener (listener , CastSession .class );
163170 } else {
164171 pendingListeners .remove (listener );
165172 }
166173 }
167174
175+ @ Nullable
176+ /* package */ MediaRouteSelector registerListenerAndGetCurrentSelector (
177+ MediaRouteSelectorListener listener ) {
178+ checkNotNull (listener );
179+ if (castContext != null ) {
180+ MediaRouteSelector selector = castContext .getMergedSelector ();
181+ return (selector == null ) ? MediaRouteSelector .EMPTY : selector ;
182+ }
183+ if (castContextLoadFailure != null ) {
184+ return MediaRouteSelector .EMPTY ;
185+ }
186+ pendingMediaRouteSelectorListeners .add (listener );
187+ return null ;
188+ }
189+
190+ /* package */ void unregisterListener (MediaRouteSelectorListener listener ) {
191+ pendingMediaRouteSelectorListeners .remove (listener );
192+ }
193+
168194 /**
169195 * Returns the ongoing Cast session, or null if there's no ongoing Cast session, or there's no
170196 * Cast context available.
171197 */
172198 @ Nullable
173199 public CastSession getCurrentCastSession () {
174- checkRunningOnMainThread ();
200+ verifyMainThread ();
175201 return castContext != null ? castContext .getSessionManager ().getCurrentCastSession () : null ;
176202 }
177203
@@ -181,14 +207,14 @@ public CastSession getCurrentCastSession() {
181207 * @param stopCasting Whether to stop the receiver application.
182208 */
183209 public void endCurrentSession (boolean stopCasting ) {
184- checkRunningOnMainThread ();
210+ verifyMainThread ();
185211 if (castContext != null ) {
186212 castContext .getSessionManager ().endCurrentSession (stopCasting );
187213 }
188214 }
189215
190216 private void setCastContext (CastContext castContext ) {
191- checkRunningOnMainThread ();
217+ verifyMainThread ();
192218 isInitOngoing = false ;
193219 this .castContext = castContext ;
194220 SessionManager sessionManager = castContext .getSessionManager ();
@@ -205,6 +231,9 @@ private void setCastContext(CastContext castContext) {
205231 }
206232 }
207233 pendingListeners .clear ();
234+ MediaRouteSelector selector = castContext .getMergedSelector ();
235+ selector = (selector == null ) ? MediaRouteSelector .EMPTY : selector ;
236+ notifyPendingMediaRouteSelectorListeners (selector );
208237 }
209238
210239 private void onCastContextLoadFailure (@ Nullable Exception exception ) {
@@ -214,25 +243,36 @@ private void onCastContextLoadFailure(@Nullable Exception exception) {
214243 ? exception
215244 : new UnknownError ("Cast context load failed with a null exception." );
216245 Log .e (TAG , "Failed to load CastContext" , castContextLoadFailure );
246+ // Cast context load has failed, the selector won't become non-empty. Notifying listeners with
247+ // an empty selector ensures that listeners are not left pending forever.
248+ notifyPendingMediaRouteSelectorListeners (MediaRouteSelector .EMPTY );
249+ }
250+
251+ private void notifyPendingMediaRouteSelectorListeners (MediaRouteSelector selector ) {
252+ for (MediaRouteSelectorListener listener : pendingMediaRouteSelectorListeners ) {
253+ listener .onMediaRouteSelectorChanged (selector );
254+ }
255+ pendingMediaRouteSelectorListeners .clear ();
217256 }
218257
219258 private CastContextWrapper () {
220259 pendingListeners = new ArrayList <>();
260+ pendingMediaRouteSelectorListeners = new HashSet <>();
221261 }
222262
223263 @ VisibleForTesting
224264 /* package */ static void reset () {
225- checkRunningOnMainThread ();
265+ verifyMainThread ();
226266 singletonInstance = null ;
227267 }
228268
229- private static void checkRunningOnMainThread () {
230- Preconditions .checkState (Looper .myLooper () == Looper .getMainLooper ());
231- }
232-
233269 @ VisibleForTesting
234270 /* package */ interface CastContextInitializer {
235271
236272 Task <CastContext > init ();
237273 }
274+
275+ /* package */ abstract static class MediaRouteSelectorListener {
276+ void onMediaRouteSelectorChanged (MediaRouteSelector selector ) {}
277+ }
238278}
0 commit comments