• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.media;
18 
19 import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
20 
21 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
22 
23 import android.annotation.NonNull;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.ServiceConnection;
28 import android.media.IMediaRoute2ProviderService;
29 import android.media.IMediaRoute2ProviderServiceCallback;
30 import android.media.MediaRoute2ProviderInfo;
31 import android.media.MediaRoute2ProviderService;
32 import android.media.RouteDiscoveryPreference;
33 import android.media.RoutingSessionInfo;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.IBinder.DeathRecipient;
38 import android.os.Looper;
39 import android.os.RemoteException;
40 import android.os.UserHandle;
41 import android.text.TextUtils;
42 import android.util.Log;
43 import android.util.Slog;
44 
45 import com.android.internal.annotations.GuardedBy;
46 
47 import java.lang.ref.WeakReference;
48 import java.util.ArrayList;
49 import java.util.Collections;
50 import java.util.List;
51 import java.util.Objects;
52 import java.util.Set;
53 
54 /**
55  * Maintains a connection to a particular {@link MediaRoute2ProviderService}.
56  */
57 final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
58         implements ServiceConnection {
59     private static final String TAG = "MR2ProviderSvcProxy";
60     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
61 
62     private final Context mContext;
63     private final int mUserId;
64     private final Handler mHandler;
65     private final boolean mIsSelfScanOnlyProvider;
66 
67     // Connection state
68     private boolean mRunning;
69     private boolean mBound;
70     private Connection mActiveConnection;
71     private boolean mConnectionReady;
72 
73     private boolean mIsManagerScanning;
74     private RouteDiscoveryPreference mLastDiscoveryPreference = null;
75     private boolean mLastDiscoveryPreferenceIncludesThisPackage = false;
76 
77     @GuardedBy("mLock")
78     final List<RoutingSessionInfo> mReleasingSessions = new ArrayList<>();
79 
MediaRoute2ProviderServiceProxy( @onNull Context context, @NonNull ComponentName componentName, boolean isSelfScanOnlyProvider, int userId)80     MediaRoute2ProviderServiceProxy(
81             @NonNull Context context,
82             @NonNull ComponentName componentName,
83             boolean isSelfScanOnlyProvider,
84             int userId) {
85         super(componentName);
86         mContext = Objects.requireNonNull(context, "Context must not be null.");
87         mIsSelfScanOnlyProvider = isSelfScanOnlyProvider;
88         mUserId = userId;
89         mHandler = new Handler(Looper.myLooper());
90     }
91 
setManagerScanning(boolean managerScanning)92     public void setManagerScanning(boolean managerScanning) {
93         if (mIsManagerScanning != managerScanning) {
94             mIsManagerScanning = managerScanning;
95             updateBinding();
96         }
97     }
98 
99     @Override
requestCreateSession(long requestId, String packageName, String routeId, Bundle sessionHints)100     public void requestCreateSession(long requestId, String packageName, String routeId,
101             Bundle sessionHints) {
102         if (mConnectionReady) {
103             mActiveConnection.requestCreateSession(requestId, packageName, routeId, sessionHints);
104             updateBinding();
105         }
106     }
107 
108     @Override
releaseSession(long requestId, String sessionId)109     public void releaseSession(long requestId, String sessionId) {
110         if (mConnectionReady) {
111             mActiveConnection.releaseSession(requestId, sessionId);
112             updateBinding();
113         }
114     }
115 
116     @Override
updateDiscoveryPreference( Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference)117     public void updateDiscoveryPreference(
118             Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference) {
119         mLastDiscoveryPreference = discoveryPreference;
120         mLastDiscoveryPreferenceIncludesThisPackage =
121                 activelyScanningPackages.contains(mComponentName.getPackageName());
122         if (mConnectionReady) {
123             mActiveConnection.updateDiscoveryPreference(discoveryPreference);
124         }
125         updateBinding();
126     }
127 
128     @Override
selectRoute(long requestId, String sessionId, String routeId)129     public void selectRoute(long requestId, String sessionId, String routeId) {
130         if (mConnectionReady) {
131             mActiveConnection.selectRoute(requestId, sessionId, routeId);
132         }
133     }
134 
135     @Override
deselectRoute(long requestId, String sessionId, String routeId)136     public void deselectRoute(long requestId, String sessionId, String routeId) {
137         if (mConnectionReady) {
138             mActiveConnection.deselectRoute(requestId, sessionId, routeId);
139         }
140     }
141 
142     @Override
transferToRoute(long requestId, String sessionId, String routeId)143     public void transferToRoute(long requestId, String sessionId, String routeId) {
144         if (mConnectionReady) {
145             mActiveConnection.transferToRoute(requestId, sessionId, routeId);
146         }
147     }
148 
149     @Override
setRouteVolume(long requestId, String routeId, int volume)150     public void setRouteVolume(long requestId, String routeId, int volume) {
151         if (mConnectionReady) {
152             mActiveConnection.setRouteVolume(requestId, routeId, volume);
153             updateBinding();
154         }
155     }
156 
157     @Override
setSessionVolume(long requestId, String sessionId, int volume)158     public void setSessionVolume(long requestId, String sessionId, int volume) {
159         if (mConnectionReady) {
160             mActiveConnection.setSessionVolume(requestId, sessionId, volume);
161             updateBinding();
162         }
163     }
164 
165     @Override
prepareReleaseSession(@onNull String sessionId)166     public void prepareReleaseSession(@NonNull String sessionId) {
167         synchronized (mLock) {
168             for (RoutingSessionInfo session : mSessionInfos) {
169                 if (TextUtils.equals(session.getId(), sessionId)) {
170                     mSessionInfos.remove(session);
171                     mReleasingSessions.add(session);
172                     break;
173                 }
174             }
175         }
176     }
177 
hasComponentName(String packageName, String className)178     public boolean hasComponentName(String packageName, String className) {
179         return mComponentName.getPackageName().equals(packageName)
180                 && mComponentName.getClassName().equals(className);
181     }
182 
start()183     public void start() {
184         if (!mRunning) {
185             if (DEBUG) {
186                 Slog.d(TAG, this + ": Starting");
187             }
188             mRunning = true;
189             updateBinding();
190         }
191     }
192 
stop()193     public void stop() {
194         if (mRunning) {
195             if (DEBUG) {
196                 Slog.d(TAG, this + ": Stopping");
197             }
198             mRunning = false;
199             updateBinding();
200         }
201     }
202 
rebindIfDisconnected()203     public void rebindIfDisconnected() {
204         //TODO: When we are connecting to the service, calling this will unbind and bind again.
205         // We'd better not unbind if we are connecting.
206         if (mActiveConnection == null && shouldBind()) {
207             unbind();
208             bind();
209         }
210     }
211 
updateBinding()212     private void updateBinding() {
213         if (shouldBind()) {
214             bind();
215         } else {
216             unbind();
217         }
218     }
219 
shouldBind()220     private boolean shouldBind() {
221         if (mRunning) {
222             boolean shouldBind =
223                     mLastDiscoveryPreference != null
224                             && !mLastDiscoveryPreference.getPreferredFeatures().isEmpty();
225             if (mIsSelfScanOnlyProvider) {
226                 shouldBind &= mLastDiscoveryPreferenceIncludesThisPackage;
227             }
228             shouldBind |= mIsManagerScanning;
229             shouldBind |= !getSessionInfos().isEmpty();
230             return shouldBind;
231         }
232         return false;
233     }
234 
bind()235     private void bind() {
236         if (!mBound) {
237             if (DEBUG) {
238                 Slog.d(TAG, this + ": Binding");
239             }
240 
241             Intent service = new Intent(MediaRoute2ProviderService.SERVICE_INTERFACE);
242             service.setComponent(mComponentName);
243             try {
244                 mBound = mContext.bindServiceAsUser(service, this,
245                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
246                         new UserHandle(mUserId));
247                 if (!mBound && DEBUG) {
248                     Slog.d(TAG, this + ": Bind failed");
249                 }
250             } catch (SecurityException ex) {
251                 if (DEBUG) {
252                     Slog.d(TAG, this + ": Bind failed", ex);
253                 }
254             }
255         }
256     }
257 
unbind()258     private void unbind() {
259         if (mBound) {
260             if (DEBUG) {
261                 Slog.d(TAG, this + ": Unbinding");
262             }
263 
264             mBound = false;
265             disconnect();
266             mContext.unbindService(this);
267         }
268     }
269 
270     @Override
onServiceConnected(ComponentName name, IBinder service)271     public void onServiceConnected(ComponentName name, IBinder service) {
272         if (DEBUG) {
273             Slog.d(TAG, this + ": Connected");
274         }
275 
276         if (mBound) {
277             disconnect();
278             IMediaRoute2ProviderService serviceBinder =
279                     IMediaRoute2ProviderService.Stub.asInterface(service);
280             if (serviceBinder != null) {
281                 Connection connection = new Connection(serviceBinder);
282                 if (connection.register()) {
283                     mActiveConnection = connection;
284                 } else {
285                     if (DEBUG) {
286                         Slog.d(TAG, this + ": Registration failed");
287                     }
288                 }
289             } else {
290                 Slog.e(TAG, this + ": Service returned invalid binder");
291             }
292         }
293     }
294 
295     @Override
onServiceDisconnected(ComponentName name)296     public void onServiceDisconnected(ComponentName name) {
297         if (DEBUG) {
298             Slog.d(TAG, this + ": Service disconnected");
299         }
300         disconnect();
301     }
302 
303     @Override
onBindingDied(ComponentName name)304     public void onBindingDied(ComponentName name) {
305         if (DEBUG) {
306             Slog.d(TAG, this + ": Service binding died");
307         }
308         unbind();
309         if (shouldBind()) {
310             bind();
311         }
312     }
313 
onConnectionReady(Connection connection)314     private void onConnectionReady(Connection connection) {
315         if (mActiveConnection == connection) {
316             mConnectionReady = true;
317             if (mLastDiscoveryPreference != null) {
318                 updateDiscoveryPreference(
319                         mLastDiscoveryPreferenceIncludesThisPackage
320                                 ? Set.of(mComponentName.getPackageName())
321                                 : Set.of(),
322                         mLastDiscoveryPreference);
323             }
324         }
325     }
326 
onConnectionDied(Connection connection)327     private void onConnectionDied(Connection connection) {
328         if (mActiveConnection == connection) {
329             if (DEBUG) {
330                 Slog.d(TAG, this + ": Service connection died");
331             }
332             disconnect();
333         }
334     }
335 
onProviderUpdated(Connection connection, MediaRoute2ProviderInfo providerInfo)336     private void onProviderUpdated(Connection connection, MediaRoute2ProviderInfo providerInfo) {
337         if (mActiveConnection != connection) {
338             return;
339         }
340         if (DEBUG) {
341             Slog.d(TAG, this + ": updated");
342         }
343         setAndNotifyProviderState(providerInfo);
344     }
345 
onSessionCreated(Connection connection, long requestId, RoutingSessionInfo newSession)346     private void onSessionCreated(Connection connection, long requestId,
347             RoutingSessionInfo newSession) {
348         if (mActiveConnection != connection) {
349             return;
350         }
351 
352         if (newSession == null) {
353             Slog.w(TAG, "onSessionCreated: Ignoring null session sent from " + mComponentName);
354             return;
355         }
356 
357         newSession = assignProviderIdForSession(newSession);
358         String newSessionId = newSession.getId();
359 
360         synchronized (mLock) {
361             if (mSessionInfos.stream()
362                     .anyMatch(session -> TextUtils.equals(session.getId(), newSessionId))
363                     || mReleasingSessions.stream()
364                     .anyMatch(session -> TextUtils.equals(session.getId(), newSessionId))) {
365                 Slog.w(TAG, "onSessionCreated: Duplicate session already exists. Ignoring.");
366                 return;
367             }
368             mSessionInfos.add(newSession);
369         }
370 
371         mCallback.onSessionCreated(this, requestId, newSession);
372     }
373 
findSessionByIdLocked(RoutingSessionInfo session)374     private int findSessionByIdLocked(RoutingSessionInfo session) {
375         for (int i = 0; i < mSessionInfos.size(); i++) {
376             if (TextUtils.equals(mSessionInfos.get(i).getId(), session.getId())) {
377                 return i;
378             }
379         }
380         return -1;
381     }
382 
383 
onSessionsUpdated(Connection connection, List<RoutingSessionInfo> sessions)384     private void onSessionsUpdated(Connection connection, List<RoutingSessionInfo> sessions) {
385         if (mActiveConnection != connection) {
386             return;
387         }
388 
389         int targetIndex = 0;
390         synchronized (mLock) {
391             for (RoutingSessionInfo session : sessions) {
392                 if (session == null) continue;
393                 session = assignProviderIdForSession(session);
394 
395                 int sourceIndex = findSessionByIdLocked(session);
396                 if (sourceIndex < 0) {
397                     mSessionInfos.add(targetIndex++, session);
398                     dispatchSessionCreated(REQUEST_ID_NONE, session);
399                 } else if (sourceIndex < targetIndex) {
400                     Slog.w(TAG, "Ignoring duplicate session ID: " + session.getId());
401                 } else {
402                     mSessionInfos.set(sourceIndex, session);
403                     Collections.swap(mSessionInfos, sourceIndex, targetIndex++);
404                     dispatchSessionUpdated(session);
405                 }
406             }
407             for (int i = mSessionInfos.size() - 1; i >= targetIndex; i--) {
408                 RoutingSessionInfo releasedSession = mSessionInfos.remove(i);
409                 dispatchSessionReleased(releasedSession);
410             }
411         }
412     }
413 
onSessionReleased(Connection connection, RoutingSessionInfo releasedSession)414     private void onSessionReleased(Connection connection, RoutingSessionInfo releasedSession) {
415         if (mActiveConnection != connection) {
416             return;
417         }
418         if (releasedSession == null) {
419             Slog.w(TAG, "onSessionReleased: Ignoring null session sent from " + mComponentName);
420             return;
421         }
422 
423         releasedSession = assignProviderIdForSession(releasedSession);
424 
425         boolean found = false;
426         synchronized (mLock) {
427             for (RoutingSessionInfo session : mSessionInfos) {
428                 if (TextUtils.equals(session.getId(), releasedSession.getId())) {
429                     mSessionInfos.remove(session);
430                     found = true;
431                     break;
432                 }
433             }
434             if (!found) {
435                 for (RoutingSessionInfo session : mReleasingSessions) {
436                     if (TextUtils.equals(session.getId(), releasedSession.getId())) {
437                         mReleasingSessions.remove(session);
438                         return;
439                     }
440                 }
441             }
442         }
443 
444         if (!found) {
445             Slog.w(TAG, "onSessionReleased: Matching session info not found");
446             return;
447         }
448 
449         mCallback.onSessionReleased(this, releasedSession);
450     }
451 
dispatchSessionCreated(long requestId, RoutingSessionInfo session)452     private void dispatchSessionCreated(long requestId, RoutingSessionInfo session) {
453         mHandler.sendMessage(
454                 obtainMessage(mCallback::onSessionCreated, this, requestId, session));
455     }
456 
dispatchSessionUpdated(RoutingSessionInfo session)457     private void dispatchSessionUpdated(RoutingSessionInfo session) {
458         mHandler.sendMessage(
459                 obtainMessage(mCallback::onSessionUpdated, this, session));
460     }
461 
dispatchSessionReleased(RoutingSessionInfo session)462     private void dispatchSessionReleased(RoutingSessionInfo session) {
463         mHandler.sendMessage(
464                 obtainMessage(mCallback::onSessionReleased, this, session));
465     }
466 
assignProviderIdForSession(RoutingSessionInfo sessionInfo)467     private RoutingSessionInfo assignProviderIdForSession(RoutingSessionInfo sessionInfo) {
468         return new RoutingSessionInfo.Builder(sessionInfo)
469                 .setOwnerPackageName(mComponentName.getPackageName())
470                 .setProviderId(getUniqueId())
471                 .build();
472     }
473 
onRequestFailed(Connection connection, long requestId, int reason)474     private void onRequestFailed(Connection connection, long requestId, int reason) {
475         if (mActiveConnection != connection) {
476             return;
477         }
478 
479         if (requestId == REQUEST_ID_NONE) {
480             Slog.w(TAG, "onRequestFailed: Ignoring requestId REQUEST_ID_NONE");
481             return;
482         }
483 
484         mCallback.onRequestFailed(this, requestId, reason);
485     }
486 
disconnect()487     private void disconnect() {
488         if (mActiveConnection != null) {
489             mConnectionReady = false;
490             mActiveConnection.dispose();
491             mActiveConnection = null;
492             setAndNotifyProviderState(null);
493             synchronized (mLock) {
494                 for (RoutingSessionInfo sessionInfo : mSessionInfos) {
495                     mCallback.onSessionReleased(this, sessionInfo);
496                 }
497                 mSessionInfos.clear();
498                 mReleasingSessions.clear();
499             }
500         }
501     }
502 
503     @Override
getDebugString()504     protected String getDebugString() {
505         return TextUtils.formatSimple(
506                 "ProviderServiceProxy - package: %s, bound: %b, connection (active:%b, ready:%b)",
507                 mComponentName.getPackageName(),
508                 mBound,
509                 mActiveConnection != null,
510                 mConnectionReady);
511     }
512 
513     private final class Connection implements DeathRecipient {
514         private final IMediaRoute2ProviderService mService;
515         private final ServiceCallbackStub mCallbackStub;
516 
Connection(IMediaRoute2ProviderService serviceBinder)517         Connection(IMediaRoute2ProviderService serviceBinder) {
518             mService = serviceBinder;
519             mCallbackStub = new ServiceCallbackStub(this);
520         }
521 
register()522         public boolean register() {
523             try {
524                 mService.asBinder().linkToDeath(this, 0);
525                 mService.setCallback(mCallbackStub);
526                 mHandler.post(() -> onConnectionReady(Connection.this));
527                 return true;
528             } catch (RemoteException ex) {
529                 binderDied();
530             }
531             return false;
532         }
533 
dispose()534         public void dispose() {
535             mService.asBinder().unlinkToDeath(this, 0);
536             mCallbackStub.dispose();
537         }
538 
requestCreateSession(long requestId, String packageName, String routeId, Bundle sessionHints)539         public void requestCreateSession(long requestId, String packageName, String routeId,
540                 Bundle sessionHints) {
541             try {
542                 mService.requestCreateSession(requestId, packageName, routeId, sessionHints);
543             } catch (RemoteException ex) {
544                 Slog.e(TAG, "requestCreateSession: Failed to deliver request.");
545             }
546         }
547 
releaseSession(long requestId, String sessionId)548         public void releaseSession(long requestId, String sessionId) {
549             try {
550                 mService.releaseSession(requestId, sessionId);
551             } catch (RemoteException ex) {
552                 Slog.e(TAG, "releaseSession: Failed to deliver request.");
553             }
554         }
555 
updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference)556         public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
557             try {
558                 mService.updateDiscoveryPreference(discoveryPreference);
559             } catch (RemoteException ex) {
560                 Slog.e(TAG, "updateDiscoveryPreference: Failed to deliver request.");
561             }
562         }
563 
selectRoute(long requestId, String sessionId, String routeId)564         public void selectRoute(long requestId, String sessionId, String routeId) {
565             try {
566                 mService.selectRoute(requestId, sessionId, routeId);
567             } catch (RemoteException ex) {
568                 Slog.e(TAG, "selectRoute: Failed to deliver request.", ex);
569             }
570         }
571 
deselectRoute(long requestId, String sessionId, String routeId)572         public void deselectRoute(long requestId, String sessionId, String routeId) {
573             try {
574                 mService.deselectRoute(requestId, sessionId, routeId);
575             } catch (RemoteException ex) {
576                 Slog.e(TAG, "deselectRoute: Failed to deliver request.", ex);
577             }
578         }
579 
transferToRoute(long requestId, String sessionId, String routeId)580         public void transferToRoute(long requestId, String sessionId, String routeId) {
581             try {
582                 mService.transferToRoute(requestId, sessionId, routeId);
583             } catch (RemoteException ex) {
584                 Slog.e(TAG, "transferToRoute: Failed to deliver request.", ex);
585             }
586         }
587 
setRouteVolume(long requestId, String routeId, int volume)588         public void setRouteVolume(long requestId, String routeId, int volume) {
589             try {
590                 mService.setRouteVolume(requestId, routeId, volume);
591             } catch (RemoteException ex) {
592                 Slog.e(TAG, "setRouteVolume: Failed to deliver request.", ex);
593             }
594         }
595 
setSessionVolume(long requestId, String sessionId, int volume)596         public void setSessionVolume(long requestId, String sessionId, int volume) {
597             try {
598                 mService.setSessionVolume(requestId, sessionId, volume);
599             } catch (RemoteException ex) {
600                 Slog.e(TAG, "setSessionVolume: Failed to deliver request.", ex);
601             }
602         }
603 
604         @Override
binderDied()605         public void binderDied() {
606             mHandler.post(() -> onConnectionDied(Connection.this));
607         }
608 
postProviderUpdated(MediaRoute2ProviderInfo providerInfo)609         void postProviderUpdated(MediaRoute2ProviderInfo providerInfo) {
610             mHandler.post(() -> onProviderUpdated(Connection.this, providerInfo));
611         }
612 
postSessionCreated(long requestId, RoutingSessionInfo sessionInfo)613         void postSessionCreated(long requestId, RoutingSessionInfo sessionInfo) {
614             mHandler.post(() -> onSessionCreated(Connection.this, requestId, sessionInfo));
615         }
616 
postSessionsUpdated(List<RoutingSessionInfo> sessionInfo)617         void postSessionsUpdated(List<RoutingSessionInfo> sessionInfo) {
618             mHandler.post(() -> onSessionsUpdated(Connection.this, sessionInfo));
619         }
620 
postSessionReleased(RoutingSessionInfo sessionInfo)621         void postSessionReleased(RoutingSessionInfo sessionInfo) {
622             mHandler.post(() -> onSessionReleased(Connection.this, sessionInfo));
623         }
624 
postRequestFailed(long requestId, int reason)625         void postRequestFailed(long requestId, int reason) {
626             mHandler.post(() -> onRequestFailed(Connection.this, requestId, reason));
627         }
628     }
629 
630     private static final class ServiceCallbackStub extends
631             IMediaRoute2ProviderServiceCallback.Stub {
632         private final WeakReference<Connection> mConnectionRef;
633 
ServiceCallbackStub(Connection connection)634         ServiceCallbackStub(Connection connection) {
635             mConnectionRef = new WeakReference<>(connection);
636         }
637 
dispose()638         public void dispose() {
639             mConnectionRef.clear();
640         }
641 
642         @Override
notifyProviderUpdated(MediaRoute2ProviderInfo providerInfo)643         public void notifyProviderUpdated(MediaRoute2ProviderInfo providerInfo) {
644             Connection connection = mConnectionRef.get();
645             if (connection != null) {
646                 connection.postProviderUpdated(providerInfo);
647             }
648         }
649 
650         @Override
notifySessionCreated(long requestId, RoutingSessionInfo sessionInfo)651         public void notifySessionCreated(long requestId, RoutingSessionInfo sessionInfo) {
652             Connection connection = mConnectionRef.get();
653             if (connection != null) {
654                 connection.postSessionCreated(requestId, sessionInfo);
655             }
656         }
657 
658         @Override
notifySessionsUpdated(List<RoutingSessionInfo> sessionInfo)659         public void notifySessionsUpdated(List<RoutingSessionInfo> sessionInfo) {
660             Connection connection = mConnectionRef.get();
661             if (connection != null) {
662                 connection.postSessionsUpdated(sessionInfo);
663             }
664         }
665 
666         @Override
notifySessionReleased(RoutingSessionInfo sessionInfo)667         public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
668             Connection connection = mConnectionRef.get();
669             if (connection != null) {
670                 connection.postSessionReleased(sessionInfo);
671             }
672         }
673 
674         @Override
notifyRequestFailed(long requestId, int reason)675         public void notifyRequestFailed(long requestId, int reason) {
676             Connection connection = mConnectionRef.get();
677             if (connection != null) {
678                 connection.postRequestFailed(requestId, reason);
679             }
680         }
681     }
682 }
683