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