• 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.annotation.Nullable;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.ServiceConnection;
29 import android.media.IMediaRoute2ProviderService;
30 import android.media.IMediaRoute2ProviderServiceCallback;
31 import android.media.MediaRoute2Info;
32 import android.media.MediaRoute2ProviderInfo;
33 import android.media.MediaRoute2ProviderService;
34 import android.media.RouteDiscoveryPreference;
35 import android.media.RoutingSessionInfo;
36 import android.os.Bundle;
37 import android.os.Handler;
38 import android.os.IBinder;
39 import android.os.IBinder.DeathRecipient;
40 import android.os.Looper;
41 import android.os.RemoteException;
42 import android.os.UserHandle;
43 import android.text.TextUtils;
44 import android.util.Log;
45 import android.util.LongSparseArray;
46 import android.util.Slog;
47 
48 import com.android.internal.annotations.GuardedBy;
49 import com.android.media.flags.Flags;
50 
51 import java.lang.ref.WeakReference;
52 import java.util.ArrayList;
53 import java.util.Collections;
54 import java.util.HashMap;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.Objects;
58 import java.util.Set;
59 
60 /** Maintains a connection to a particular {@link MediaRoute2ProviderService}. */
61 final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
62     private static final String TAG = "MR2ProviderSvcProxy";
63     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
64 
65     private final Context mContext;
66     private final int mUserId;
67     private final Handler mHandler;
68     private final boolean mIsSelfScanOnlyProvider;
69     private final ServiceConnection mServiceConnection = new ServiceConnectionImpl();
70 
71     // Connection state
72     private boolean mRunning;
73     private boolean mBound;
74     private Connection mActiveConnection;
75     private boolean mConnectionReady;
76 
77     private boolean mIsManagerScanning;
78     private RouteDiscoveryPreference mLastDiscoveryPreference = null;
79     private boolean mLastDiscoveryPreferenceIncludesThisPackage = false;
80 
81     @GuardedBy("mLock")
82     private final List<RoutingSessionInfo> mReleasingSessions = new ArrayList<>();
83 
84     // We keep pending requests for transfers and sessions creation separately because transfers
85     // don't have an associated request id and session creations don't have a session id.
86     @GuardedBy("mLock")
87     private final LongSparseArray<SessionCreationOrTransferRequest>
88             mRequestIdToSessionCreationRequest;
89 
90     @GuardedBy("mLock")
91     private final Map<String, SessionCreationOrTransferRequest> mSessionOriginalIdToTransferRequest;
92 
MediaRoute2ProviderServiceProxy( @onNull Context context, @NonNull Looper looper, @NonNull ComponentName componentName, boolean isSelfScanOnlyProvider, int userId)93     MediaRoute2ProviderServiceProxy(
94             @NonNull Context context,
95             @NonNull Looper looper,
96             @NonNull ComponentName componentName,
97             boolean isSelfScanOnlyProvider,
98             int userId) {
99         super(componentName);
100         mContext = Objects.requireNonNull(context, "Context must not be null.");
101         mRequestIdToSessionCreationRequest = new LongSparseArray<>();
102         mSessionOriginalIdToTransferRequest = new HashMap<>();
103         mIsSelfScanOnlyProvider = isSelfScanOnlyProvider;
104         mUserId = userId;
105         mHandler = new Handler(looper);
106     }
107 
setManagerScanning(boolean managerScanning)108     public void setManagerScanning(boolean managerScanning) {
109         if (mIsManagerScanning != managerScanning) {
110             mIsManagerScanning = managerScanning;
111             updateBinding();
112         }
113     }
114 
115     @Override
requestCreateSession( long requestId, String packageName, String routeOriginalId, Bundle sessionHints, @RoutingSessionInfo.TransferReason int transferReason, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName)116     public void requestCreateSession(
117             long requestId,
118             String packageName,
119             String routeOriginalId,
120             Bundle sessionHints,
121             @RoutingSessionInfo.TransferReason int transferReason,
122             @NonNull UserHandle transferInitiatorUserHandle,
123             @NonNull String transferInitiatorPackageName) {
124         if (mConnectionReady) {
125             if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
126                 synchronized (mLock) {
127                     mRequestIdToSessionCreationRequest.put(
128                             requestId,
129                             new SessionCreationOrTransferRequest(
130                                     requestId,
131                                     routeOriginalId,
132                                     transferReason,
133                                     transferInitiatorUserHandle,
134                                     transferInitiatorPackageName));
135                 }
136             }
137             mActiveConnection.requestCreateSession(
138                     requestId, packageName, routeOriginalId, sessionHints);
139             updateBinding();
140         }
141     }
142 
143     @Override
releaseSession(long requestId, String sessionId)144     public void releaseSession(long requestId, String sessionId) {
145         if (mConnectionReady) {
146             if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
147                 synchronized (mLock) {
148                     mSessionOriginalIdToTransferRequest.remove(sessionId);
149                 }
150             }
151             mActiveConnection.releaseSession(requestId, sessionId);
152             updateBinding();
153         }
154     }
155 
156     @Override
updateDiscoveryPreference( Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference)157     public void updateDiscoveryPreference(
158             Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference) {
159         mLastDiscoveryPreference = discoveryPreference;
160         mLastDiscoveryPreferenceIncludesThisPackage =
161                 activelyScanningPackages.contains(mComponentName.getPackageName());
162         if (mConnectionReady) {
163             mActiveConnection.updateDiscoveryPreference(discoveryPreference);
164         }
165         updateBinding();
166     }
167 
168     @Override
selectRoute(long requestId, String sessionId, String routeId)169     public void selectRoute(long requestId, String sessionId, String routeId) {
170         if (mConnectionReady) {
171             mActiveConnection.selectRoute(requestId, sessionId, routeId);
172         }
173     }
174 
175     @Override
deselectRoute(long requestId, String sessionId, String routeId)176     public void deselectRoute(long requestId, String sessionId, String routeId) {
177         if (mConnectionReady) {
178             mActiveConnection.deselectRoute(requestId, sessionId, routeId);
179         }
180     }
181 
182     @Override
transferToRoute( long requestId, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName, String sessionOriginalId, String routeOriginalId, @RoutingSessionInfo.TransferReason int transferReason)183     public void transferToRoute(
184             long requestId,
185             @NonNull UserHandle transferInitiatorUserHandle,
186             @NonNull String transferInitiatorPackageName,
187             String sessionOriginalId,
188             String routeOriginalId,
189             @RoutingSessionInfo.TransferReason int transferReason) {
190         if (mConnectionReady) {
191             if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
192                 synchronized (mLock) {
193                     mSessionOriginalIdToTransferRequest.put(
194                             sessionOriginalId,
195                             new SessionCreationOrTransferRequest(
196                                     requestId,
197                                     routeOriginalId,
198                                     transferReason,
199                                     transferInitiatorUserHandle,
200                                     transferInitiatorPackageName));
201                 }
202             }
203             mActiveConnection.transferToRoute(requestId, sessionOriginalId, routeOriginalId);
204         }
205     }
206 
207     @Override
setRouteVolume(long requestId, String routeOriginalId, int volume)208     public void setRouteVolume(long requestId, String routeOriginalId, int volume) {
209         if (mConnectionReady) {
210             mActiveConnection.setRouteVolume(requestId, routeOriginalId, volume);
211             updateBinding();
212         }
213     }
214 
215     @Override
setSessionVolume(long requestId, String sessionOriginalId, int volume)216     public void setSessionVolume(long requestId, String sessionOriginalId, int volume) {
217         if (mConnectionReady) {
218             mActiveConnection.setSessionVolume(requestId, sessionOriginalId, volume);
219             updateBinding();
220         }
221     }
222 
223     @Override
prepareReleaseSession(@onNull String sessionUniqueId)224     public void prepareReleaseSession(@NonNull String sessionUniqueId) {
225         synchronized (mLock) {
226             for (RoutingSessionInfo session : mSessionInfos) {
227                 if (TextUtils.equals(session.getId(), sessionUniqueId)) {
228                     mSessionInfos.remove(session);
229                     mReleasingSessions.add(session);
230                     break;
231                 }
232             }
233         }
234     }
235 
hasComponentName(String packageName, String className)236     public boolean hasComponentName(String packageName, String className) {
237         return mComponentName.getPackageName().equals(packageName)
238                 && mComponentName.getClassName().equals(className);
239     }
240 
start(boolean rebindIfDisconnected)241     public void start(boolean rebindIfDisconnected) {
242         if (!mRunning) {
243             if (DEBUG) {
244                 Slog.d(TAG, this + ": Starting");
245             }
246             mRunning = true;
247             if (!Flags.enablePreventionOfKeepAliveRouteProviders()) {
248                 updateBinding();
249             }
250         }
251         if (rebindIfDisconnected && mActiveConnection == null && shouldBind()) {
252             unbind();
253             bind();
254         }
255     }
256 
stop()257     public void stop() {
258         if (mRunning) {
259             if (DEBUG) {
260                 Slog.d(TAG, this + ": Stopping");
261             }
262             mRunning = false;
263             updateBinding();
264         }
265     }
266 
updateBinding()267     private void updateBinding() {
268         if (shouldBind()) {
269             bind();
270         } else {
271             unbind();
272         }
273     }
274 
shouldBind()275     private boolean shouldBind() {
276         if (!mRunning) {
277             return false;
278         }
279         boolean bindDueToManagerScan =
280                 mIsManagerScanning && !Flags.enablePreventionOfManagerScansWhenNoAppsScan();
281         if (!getSessionInfos().isEmpty() || bindDueToManagerScan) {
282             // We bind if any manager is scanning (regardless of whether an app is scanning) to give
283             // the opportunity for providers to publish routing sessions that were established
284             // directly between the app and the provider (typically via AndroidX MediaRouter). See
285             // b/176774510#comment20 for more information.
286             return true;
287         }
288         boolean anAppIsScanning =
289                 mLastDiscoveryPreference != null
290                         && !mLastDiscoveryPreference.getPreferredFeatures().isEmpty();
291         return anAppIsScanning
292                 && (mLastDiscoveryPreferenceIncludesThisPackage || !mIsSelfScanOnlyProvider);
293     }
294 
bind()295     private void bind() {
296         if (!mBound) {
297             if (DEBUG) {
298                 Slog.d(TAG, this + ": Binding");
299             }
300 
301             Intent service = new Intent(MediaRoute2ProviderService.SERVICE_INTERFACE);
302             service.setComponent(mComponentName);
303             try {
304                 mBound =
305                         mContext.bindServiceAsUser(
306                                 service,
307                                 mServiceConnection,
308                                 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
309                                 new UserHandle(mUserId));
310                 if (!mBound && DEBUG) {
311                     Slog.d(TAG, this + ": Bind failed");
312                 }
313             } catch (SecurityException ex) {
314                 if (DEBUG) {
315                     Slog.d(TAG, this + ": Bind failed", ex);
316                 }
317             }
318         }
319     }
320 
unbind()321     private void unbind() {
322         if (mBound) {
323             if (DEBUG) {
324                 Slog.d(TAG, this + ": Unbinding");
325             }
326 
327             mBound = false;
328             disconnect();
329             mContext.unbindService(mServiceConnection);
330         }
331     }
332 
onServiceConnectedInternal(IBinder service)333     private void onServiceConnectedInternal(IBinder service) {
334         if (DEBUG) {
335             Slog.d(TAG, this + ": Connected");
336         }
337 
338         if (mBound) {
339             disconnect();
340             IMediaRoute2ProviderService serviceBinder =
341                     IMediaRoute2ProviderService.Stub.asInterface(service);
342             if (serviceBinder != null) {
343                 Connection connection = new Connection(serviceBinder);
344                 if (connection.register()) {
345                     mActiveConnection = connection;
346                 } else {
347                     if (DEBUG) {
348                         Slog.d(TAG, this + ": Registration failed");
349                     }
350                 }
351             } else {
352                 Slog.e(TAG, this + ": Service returned invalid binder");
353             }
354         }
355     }
356 
onServiceDisconnectedInternal()357     private void onServiceDisconnectedInternal() {
358         if (DEBUG) {
359             Slog.d(TAG, this + ": Service disconnected");
360         }
361         disconnect();
362     }
363 
onBindingDiedInternal(ComponentName name)364     private void onBindingDiedInternal(ComponentName name) {
365         unbind();
366         if (Flags.enablePreventionOfKeepAliveRouteProviders()) {
367             Slog.w(
368                     TAG,
369                     TextUtils.formatSimple(
370                             "Route provider service (%s) binding died, but we did not rebind.",
371                             name.toString()));
372         } else if (shouldBind()) {
373             Slog.w(
374                     TAG,
375                     TextUtils.formatSimple(
376                             "Rebound to provider service (%s) after binding died.",
377                             name.toString()));
378             bind();
379         }
380     }
381 
onConnectionReady(Connection connection)382     private void onConnectionReady(Connection connection) {
383         if (mActiveConnection == connection) {
384             mConnectionReady = true;
385             if (mLastDiscoveryPreference != null) {
386                 updateDiscoveryPreference(
387                         mLastDiscoveryPreferenceIncludesThisPackage
388                                 ? Set.of(mComponentName.getPackageName())
389                                 : Set.of(),
390                         mLastDiscoveryPreference);
391             }
392         }
393     }
394 
onConnectionDied(Connection connection)395     private void onConnectionDied(Connection connection) {
396         if (mActiveConnection == connection) {
397             if (DEBUG) {
398                 Slog.d(TAG, this + ": Service connection died");
399             }
400             disconnect();
401         }
402     }
403 
onProviderUpdated(Connection connection, MediaRoute2ProviderInfo providerInfo)404     private void onProviderUpdated(Connection connection, MediaRoute2ProviderInfo providerInfo) {
405         if (mActiveConnection != connection) {
406             return;
407         }
408         if (DEBUG) {
409             Slog.d(TAG, this + ": updated");
410         }
411         setAndNotifyProviderState(providerInfo);
412     }
413 
onSessionCreated(Connection connection, long requestId, RoutingSessionInfo newSession)414     private void onSessionCreated(Connection connection, long requestId,
415             RoutingSessionInfo newSession) {
416         if (mActiveConnection != connection) {
417             return;
418         }
419 
420         if (newSession == null) {
421             Slog.w(TAG, "onSessionCreated: Ignoring null session sent from " + mComponentName);
422             return;
423         }
424 
425         newSession = assignProviderIdForSession(newSession);
426         String newSessionId = newSession.getId();
427 
428         synchronized (mLock) {
429             if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
430                 newSession =
431                         createSessionWithPopulatedTransferInitiationDataLocked(
432                                 requestId, /* oldSessionInfo= */ null, newSession);
433             }
434             if (mSessionInfos.stream()
435                     .anyMatch(session -> TextUtils.equals(session.getId(), newSessionId))
436                     || mReleasingSessions.stream()
437                     .anyMatch(session -> TextUtils.equals(session.getId(), newSessionId))) {
438                 Slog.w(TAG, "onSessionCreated: Duplicate session already exists. Ignoring.");
439                 return;
440             }
441             mSessionInfos.add(newSession);
442         }
443 
444         mCallback.onSessionCreated(this, requestId, newSession);
445     }
446 
447     @GuardedBy("mLock")
findSessionByIdLocked(RoutingSessionInfo session)448     private int findSessionByIdLocked(RoutingSessionInfo session) {
449         for (int i = 0; i < mSessionInfos.size(); i++) {
450             if (TextUtils.equals(mSessionInfos.get(i).getId(), session.getId())) {
451                 return i;
452             }
453         }
454         return -1;
455     }
456 
457 
onSessionsUpdated(Connection connection, List<RoutingSessionInfo> sessions)458     private void onSessionsUpdated(Connection connection, List<RoutingSessionInfo> sessions) {
459         if (mActiveConnection != connection) {
460             return;
461         }
462 
463         int targetIndex = 0;
464         synchronized (mLock) {
465             for (RoutingSessionInfo session : sessions) {
466                 if (session == null) continue;
467                 session = assignProviderIdForSession(session);
468                 int sourceIndex = findSessionByIdLocked(session);
469                 if (sourceIndex < 0) {
470                     mSessionInfos.add(targetIndex++, session);
471                     dispatchSessionCreated(REQUEST_ID_NONE, session);
472                 } else if (sourceIndex < targetIndex) {
473                     Slog.w(TAG, "Ignoring duplicate session ID: " + session.getId());
474                 } else {
475                     if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
476                         RoutingSessionInfo oldSessionInfo = mSessionInfos.get(sourceIndex);
477                         session =
478                                 createSessionWithPopulatedTransferInitiationDataLocked(
479                                         REQUEST_ID_NONE, oldSessionInfo, session);
480                     }
481                     mSessionInfos.set(sourceIndex, session);
482                     Collections.swap(mSessionInfos, sourceIndex, targetIndex++);
483                     dispatchSessionUpdated(session);
484                 }
485             }
486             for (int i = mSessionInfos.size() - 1; i >= targetIndex; i--) {
487                 RoutingSessionInfo releasedSession = mSessionInfos.remove(i);
488                 mSessionOriginalIdToTransferRequest.remove(releasedSession.getId());
489                 dispatchSessionReleased(releasedSession);
490             }
491         }
492     }
493 
494     /**
495      * Returns a {@link RoutingSessionInfo} with transfer initiation data from the given {@code
496      * oldSessionInfo}, and any pending transfer or session creation requests.
497      */
498     @GuardedBy("mLock")
createSessionWithPopulatedTransferInitiationDataLocked( long requestId, @Nullable RoutingSessionInfo oldSessionInfo, @NonNull RoutingSessionInfo newSessionInfo)499     private RoutingSessionInfo createSessionWithPopulatedTransferInitiationDataLocked(
500             long requestId,
501             @Nullable RoutingSessionInfo oldSessionInfo,
502             @NonNull RoutingSessionInfo newSessionInfo) {
503         SessionCreationOrTransferRequest pendingRequest =
504                 oldSessionInfo != null
505                         ? mSessionOriginalIdToTransferRequest.get(newSessionInfo.getOriginalId())
506                         : mRequestIdToSessionCreationRequest.get(requestId);
507         boolean pendingTargetRouteInSelectedRoutes =
508                 pendingRequest != null
509                         && pendingRequest.isTargetRouteIdInRouteUniqueIdList(
510                                 newSessionInfo.getSelectedRoutes());
511         boolean pendingTargetRouteInTransferableRoutes =
512                 pendingRequest != null
513                         && pendingRequest.isTargetRouteIdInRouteUniqueIdList(
514                                 newSessionInfo.getTransferableRoutes());
515 
516         int transferReason;
517         UserHandle transferInitiatorUserHandle;
518         String transferInitiatorPackageName;
519         if (pendingTargetRouteInSelectedRoutes) { // The pending request has been satisfied.
520             transferReason = pendingRequest.mTransferReason;
521             transferInitiatorUserHandle = pendingRequest.mTransferInitiatorUserHandle;
522             transferInitiatorPackageName = pendingRequest.mTransferInitiatorPackageName;
523         } else if (oldSessionInfo != null) {
524             // No pending request, we copy the values from the old session object.
525             transferReason = oldSessionInfo.getTransferReason();
526             transferInitiatorUserHandle = oldSessionInfo.getTransferInitiatorUserHandle();
527             transferInitiatorPackageName = oldSessionInfo.getTransferInitiatorPackageName();
528         } else { // There's a new session with no associated creation request, we use defaults.
529             transferReason = RoutingSessionInfo.TRANSFER_REASON_FALLBACK;
530             transferInitiatorUserHandle = UserHandle.of(mUserId);
531             transferInitiatorPackageName = newSessionInfo.getClientPackageName();
532         }
533         if (pendingTargetRouteInSelectedRoutes || !pendingTargetRouteInTransferableRoutes) {
534             // The pending request has been satisfied, or the target route is no longer available.
535             if (oldSessionInfo != null) {
536                 mSessionOriginalIdToTransferRequest.remove(newSessionInfo.getId());
537             } else if (pendingRequest != null) {
538                 mRequestIdToSessionCreationRequest.remove(pendingRequest.mRequestId);
539             }
540         }
541         return new RoutingSessionInfo.Builder(newSessionInfo)
542                 .setTransferInitiator(transferInitiatorUserHandle, transferInitiatorPackageName)
543                 .setTransferReason(transferReason)
544                 .build();
545     }
546 
onSessionReleased(Connection connection, RoutingSessionInfo releasedSession)547     private void onSessionReleased(Connection connection, RoutingSessionInfo releasedSession) {
548         if (mActiveConnection != connection) {
549             return;
550         }
551         if (releasedSession == null) {
552             Slog.w(TAG, "onSessionReleased: Ignoring null session sent from " + mComponentName);
553             return;
554         }
555 
556         releasedSession = assignProviderIdForSession(releasedSession);
557 
558         boolean found = false;
559         synchronized (mLock) {
560             mSessionOriginalIdToTransferRequest.remove(releasedSession.getId());
561             for (RoutingSessionInfo session : mSessionInfos) {
562                 if (TextUtils.equals(session.getId(), releasedSession.getId())) {
563                     mSessionInfos.remove(session);
564                     found = true;
565                     break;
566                 }
567             }
568             if (!found) {
569                 for (RoutingSessionInfo session : mReleasingSessions) {
570                     if (TextUtils.equals(session.getId(), releasedSession.getId())) {
571                         mReleasingSessions.remove(session);
572                         return;
573                     }
574                 }
575             }
576         }
577 
578         if (!found) {
579             Slog.w(TAG, "onSessionReleased: Matching session info not found");
580             return;
581         }
582 
583         mCallback.onSessionReleased(this, releasedSession);
584     }
585 
dispatchSessionCreated(long requestId, RoutingSessionInfo session)586     private void dispatchSessionCreated(long requestId, RoutingSessionInfo session) {
587         mHandler.sendMessage(
588                 obtainMessage(mCallback::onSessionCreated, this, requestId, session));
589     }
590 
dispatchSessionUpdated(RoutingSessionInfo session)591     private void dispatchSessionUpdated(RoutingSessionInfo session) {
592         mHandler.sendMessage(
593                 obtainMessage(mCallback::onSessionUpdated, this, session));
594     }
595 
dispatchSessionReleased(RoutingSessionInfo session)596     private void dispatchSessionReleased(RoutingSessionInfo session) {
597         mHandler.sendMessage(
598                 obtainMessage(mCallback::onSessionReleased, this, session));
599     }
600 
assignProviderIdForSession(RoutingSessionInfo sessionInfo)601     private RoutingSessionInfo assignProviderIdForSession(RoutingSessionInfo sessionInfo) {
602         return new RoutingSessionInfo.Builder(sessionInfo)
603                 .setOwnerPackageName(mComponentName.getPackageName())
604                 .setProviderId(getUniqueId())
605                 .build();
606     }
607 
onRequestFailed(Connection connection, long requestId, int reason)608     private void onRequestFailed(Connection connection, long requestId, int reason) {
609         if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
610             synchronized (mLock) {
611                 mRequestIdToSessionCreationRequest.remove(requestId);
612             }
613         }
614         if (mActiveConnection != connection) {
615             return;
616         }
617 
618         if (requestId == REQUEST_ID_NONE) {
619             Slog.w(TAG, "onRequestFailed: Ignoring requestId REQUEST_ID_NONE");
620             return;
621         }
622 
623         mCallback.onRequestFailed(this, requestId, reason);
624     }
625 
disconnect()626     private void disconnect() {
627         if (mActiveConnection != null) {
628             mConnectionReady = false;
629             mActiveConnection.dispose();
630             mActiveConnection = null;
631             setAndNotifyProviderState(null);
632             synchronized (mLock) {
633                 for (RoutingSessionInfo sessionInfo : mSessionInfos) {
634                     mCallback.onSessionReleased(this, sessionInfo);
635                 }
636                 mSessionInfos.clear();
637                 mReleasingSessions.clear();
638                 mRequestIdToSessionCreationRequest.clear();
639                 mSessionOriginalIdToTransferRequest.clear();
640             }
641         }
642     }
643 
644     @Override
getDebugString()645     protected String getDebugString() {
646         int pendingSessionCreationCount;
647         int pendingTransferCount;
648         synchronized (mLock) {
649             pendingSessionCreationCount = mRequestIdToSessionCreationRequest.size();
650             pendingTransferCount = mSessionOriginalIdToTransferRequest.size();
651         }
652         return TextUtils.formatSimple(
653                 "ProviderServiceProxy - package: %s, bound: %b, connection (active:%b, ready:%b), "
654                         + "pending (session creations: %d, transfers: %d)",
655                 mComponentName.getPackageName(),
656                 mBound,
657                 mActiveConnection != null,
658                 mConnectionReady,
659                 pendingSessionCreationCount,
660                 pendingTransferCount);
661     }
662 
663     // All methods in this class are called on the main thread.
664     private final class ServiceConnectionImpl implements ServiceConnection {
665 
666         @Override
onServiceConnected(ComponentName name, IBinder service)667         public void onServiceConnected(ComponentName name, IBinder service) {
668             if (Flags.enableMr2ServiceNonMainBgThread()) {
669                 mHandler.post(() -> onServiceConnectedInternal(service));
670             } else {
671                 onServiceConnectedInternal(service);
672             }
673         }
674 
675         @Override
onServiceDisconnected(ComponentName name)676         public void onServiceDisconnected(ComponentName name) {
677             if (Flags.enableMr2ServiceNonMainBgThread()) {
678                 mHandler.post(() -> onServiceDisconnectedInternal());
679             } else {
680                 onServiceDisconnectedInternal();
681             }
682         }
683 
684         @Override
onBindingDied(ComponentName name)685         public void onBindingDied(ComponentName name) {
686             if (Flags.enableMr2ServiceNonMainBgThread()) {
687                 mHandler.post(() -> onBindingDiedInternal(name));
688             } else {
689                 onBindingDiedInternal(name);
690             }
691         }
692     }
693 
694     private final class Connection implements DeathRecipient {
695         private final IMediaRoute2ProviderService mService;
696         private final ServiceCallbackStub mCallbackStub;
697 
Connection(IMediaRoute2ProviderService serviceBinder)698         Connection(IMediaRoute2ProviderService serviceBinder) {
699             mService = serviceBinder;
700             mCallbackStub = new ServiceCallbackStub(this);
701         }
702 
register()703         public boolean register() {
704             try {
705                 mService.asBinder().linkToDeath(this, 0);
706                 mService.setCallback(mCallbackStub);
707                 mHandler.post(() -> onConnectionReady(Connection.this));
708                 return true;
709             } catch (RemoteException ex) {
710                 binderDied();
711             }
712             return false;
713         }
714 
dispose()715         public void dispose() {
716             mService.asBinder().unlinkToDeath(this, 0);
717             mCallbackStub.dispose();
718         }
719 
requestCreateSession(long requestId, String packageName, String routeId, Bundle sessionHints)720         public void requestCreateSession(long requestId, String packageName, String routeId,
721                 Bundle sessionHints) {
722             try {
723                 mService.requestCreateSession(requestId, packageName, routeId, sessionHints);
724             } catch (RemoteException ex) {
725                 Slog.e(TAG, "requestCreateSession: Failed to deliver request.");
726             }
727         }
728 
releaseSession(long requestId, String sessionId)729         public void releaseSession(long requestId, String sessionId) {
730             try {
731                 mService.releaseSession(requestId, sessionId);
732             } catch (RemoteException ex) {
733                 Slog.e(TAG, "releaseSession: Failed to deliver request.");
734             }
735         }
736 
updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference)737         public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
738             try {
739                 mService.updateDiscoveryPreference(discoveryPreference);
740             } catch (RemoteException ex) {
741                 Slog.e(TAG, "updateDiscoveryPreference: Failed to deliver request.");
742             }
743         }
744 
selectRoute(long requestId, String sessionId, String routeId)745         public void selectRoute(long requestId, String sessionId, String routeId) {
746             try {
747                 mService.selectRoute(requestId, sessionId, routeId);
748             } catch (RemoteException ex) {
749                 Slog.e(TAG, "selectRoute: Failed to deliver request.", ex);
750             }
751         }
752 
deselectRoute(long requestId, String sessionId, String routeId)753         public void deselectRoute(long requestId, String sessionId, String routeId) {
754             try {
755                 mService.deselectRoute(requestId, sessionId, routeId);
756             } catch (RemoteException ex) {
757                 Slog.e(TAG, "deselectRoute: Failed to deliver request.", ex);
758             }
759         }
760 
transferToRoute(long requestId, String sessionId, String routeId)761         public void transferToRoute(long requestId, String sessionId, String routeId) {
762             try {
763                 mService.transferToRoute(requestId, sessionId, routeId);
764             } catch (RemoteException ex) {
765                 Slog.e(TAG, "transferToRoute: Failed to deliver request.", ex);
766             }
767         }
768 
setRouteVolume(long requestId, String routeId, int volume)769         public void setRouteVolume(long requestId, String routeId, int volume) {
770             try {
771                 mService.setRouteVolume(requestId, routeId, volume);
772             } catch (RemoteException ex) {
773                 Slog.e(TAG, "setRouteVolume: Failed to deliver request.", ex);
774             }
775         }
776 
setSessionVolume(long requestId, String sessionId, int volume)777         public void setSessionVolume(long requestId, String sessionId, int volume) {
778             try {
779                 mService.setSessionVolume(requestId, sessionId, volume);
780             } catch (RemoteException ex) {
781                 Slog.e(TAG, "setSessionVolume: Failed to deliver request.", ex);
782             }
783         }
784 
785         @Override
binderDied()786         public void binderDied() {
787             mHandler.post(() -> onConnectionDied(Connection.this));
788         }
789 
postProviderUpdated(MediaRoute2ProviderInfo providerInfo)790         void postProviderUpdated(MediaRoute2ProviderInfo providerInfo) {
791             mHandler.post(() -> onProviderUpdated(Connection.this, providerInfo));
792         }
793 
postSessionCreated(long requestId, RoutingSessionInfo sessionInfo)794         void postSessionCreated(long requestId, RoutingSessionInfo sessionInfo) {
795             mHandler.post(() -> onSessionCreated(Connection.this, requestId, sessionInfo));
796         }
797 
postSessionsUpdated(List<RoutingSessionInfo> sessionInfo)798         void postSessionsUpdated(List<RoutingSessionInfo> sessionInfo) {
799             mHandler.post(() -> onSessionsUpdated(Connection.this, sessionInfo));
800         }
801 
postSessionReleased(RoutingSessionInfo sessionInfo)802         void postSessionReleased(RoutingSessionInfo sessionInfo) {
803             mHandler.post(() -> onSessionReleased(Connection.this, sessionInfo));
804         }
805 
postRequestFailed(long requestId, int reason)806         void postRequestFailed(long requestId, int reason) {
807             mHandler.post(() -> onRequestFailed(Connection.this, requestId, reason));
808         }
809     }
810 
811     private static final class ServiceCallbackStub extends
812             IMediaRoute2ProviderServiceCallback.Stub {
813         private final WeakReference<Connection> mConnectionRef;
814 
ServiceCallbackStub(Connection connection)815         ServiceCallbackStub(Connection connection) {
816             mConnectionRef = new WeakReference<>(connection);
817         }
818 
dispose()819         public void dispose() {
820             mConnectionRef.clear();
821         }
822 
823         @Override
notifyProviderUpdated(@onNull MediaRoute2ProviderInfo providerInfo)824         public void notifyProviderUpdated(@NonNull MediaRoute2ProviderInfo providerInfo) {
825             Objects.requireNonNull(providerInfo, "providerInfo must not be null");
826 
827             for (MediaRoute2Info route : providerInfo.getRoutes()) {
828                 if (route.isSystemRoute()) {
829                     throw new SecurityException(
830                             "Only the system is allowed to publish system routes. "
831                                     + "Disallowed route: "
832                                     + route);
833                 }
834 
835                 if (route.getSuitabilityStatus()
836                         == MediaRoute2Info.SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER) {
837                     throw new SecurityException(
838                             "Only the system is allowed to set not suitable for transfer status. "
839                                     + "Disallowed route: "
840                                     + route);
841                 }
842 
843                 if (route.isSystemRouteType()) {
844                     throw new SecurityException(
845                             "Only the system is allowed to publish routes with system route types. "
846                                     + "Disallowed route: "
847                                     + route);
848                 }
849             }
850 
851             Connection connection = mConnectionRef.get();
852             if (connection != null) {
853                 connection.postProviderUpdated(providerInfo);
854             }
855         }
856 
857         @Override
notifySessionCreated(long requestId, RoutingSessionInfo sessionInfo)858         public void notifySessionCreated(long requestId, RoutingSessionInfo sessionInfo) {
859             Connection connection = mConnectionRef.get();
860             if (connection != null) {
861                 connection.postSessionCreated(requestId, sessionInfo);
862             }
863         }
864 
865         @Override
notifySessionsUpdated(List<RoutingSessionInfo> sessionInfo)866         public void notifySessionsUpdated(List<RoutingSessionInfo> sessionInfo) {
867             Connection connection = mConnectionRef.get();
868             if (connection != null) {
869                 connection.postSessionsUpdated(sessionInfo);
870             }
871         }
872 
873         @Override
notifySessionReleased(RoutingSessionInfo sessionInfo)874         public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
875             Connection connection = mConnectionRef.get();
876             if (connection != null) {
877                 connection.postSessionReleased(sessionInfo);
878             }
879         }
880 
881         @Override
notifyRequestFailed(long requestId, int reason)882         public void notifyRequestFailed(long requestId, int reason) {
883             Connection connection = mConnectionRef.get();
884             if (connection != null) {
885                 connection.postRequestFailed(requestId, reason);
886             }
887         }
888     }
889 }
890