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.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
20 import static android.content.Intent.ACTION_SCREEN_OFF;
21 import static android.content.Intent.ACTION_SCREEN_ON;
22 import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
23 import static android.media.MediaRouter2.SCANNING_STATE_NOT_SCANNING;
24 import static android.media.MediaRouter2.SCANNING_STATE_SCANNING_FULL;
25 import static android.media.MediaRouter2.SCANNING_STATE_WHILE_INTERACTIVE;
26 import static android.media.MediaRouter2Utils.getOriginalId;
27 import static android.media.MediaRouter2Utils.getProviderId;
28 
29 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
30 
31 import android.Manifest;
32 import android.annotation.NonNull;
33 import android.annotation.Nullable;
34 import android.annotation.RequiresPermission;
35 import android.app.ActivityManager;
36 import android.app.AppOpsManager;
37 import android.content.BroadcastReceiver;
38 import android.content.ComponentName;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.IntentFilter;
42 import android.content.PermissionChecker;
43 import android.content.pm.PackageManager;
44 import android.media.IMediaRouter2;
45 import android.media.IMediaRouter2Manager;
46 import android.media.MediaRoute2Info;
47 import android.media.MediaRoute2ProviderInfo;
48 import android.media.MediaRoute2ProviderService;
49 import android.media.MediaRouter2.ScanningState;
50 import android.media.MediaRouter2Manager;
51 import android.media.RouteDiscoveryPreference;
52 import android.media.RouteListingPreference;
53 import android.media.RoutingSessionInfo;
54 import android.os.Binder;
55 import android.os.Bundle;
56 import android.os.Handler;
57 import android.os.IBinder;
58 import android.os.Looper;
59 import android.os.PowerManager;
60 import android.os.RemoteException;
61 import android.os.UserHandle;
62 import android.text.TextUtils;
63 import android.util.ArrayMap;
64 import android.util.Log;
65 import android.util.Slog;
66 import android.util.SparseArray;
67 
68 import com.android.internal.annotations.GuardedBy;
69 import com.android.internal.util.function.pooled.PooledLambda;
70 import com.android.media.flags.Flags;
71 import com.android.server.LocalServices;
72 import com.android.server.pm.UserManagerInternal;
73 import com.android.server.statusbar.StatusBarManagerInternal;
74 
75 import java.io.PrintWriter;
76 import java.lang.ref.WeakReference;
77 import java.util.ArrayList;
78 import java.util.Arrays;
79 import java.util.Collection;
80 import java.util.Collections;
81 import java.util.HashSet;
82 import java.util.List;
83 import java.util.Map;
84 import java.util.Objects;
85 import java.util.Optional;
86 import java.util.Set;
87 import java.util.concurrent.CopyOnWriteArrayList;
88 import java.util.concurrent.atomic.AtomicBoolean;
89 import java.util.concurrent.atomic.AtomicInteger;
90 import java.util.stream.Collectors;
91 
92 /**
93  * Implements features related to {@link android.media.MediaRouter2} and
94  * {@link android.media.MediaRouter2Manager}.
95  */
96 class MediaRouter2ServiceImpl {
97     private static final String TAG = "MR2ServiceImpl";
98     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
99 
100     // TODO: (In Android S or later) if we add callback methods for generic failures
101     //       in MediaRouter2, remove this constant and replace the usages with the real request IDs.
102     private static final long DUMMY_REQUEST_ID = -1;
103 
104     private static final int REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING = IMPORTANCE_FOREGROUND;
105 
106     /**
107      * Contains the list of bluetooth permissions that are required to do system routing.
108      *
109      * <p>Alternatively, apps that hold {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} are
110      * also allowed to do system routing.
111      */
112     private static final String[] BLUETOOTH_PERMISSIONS_FOR_SYSTEM_ROUTING =
113             new String[] {
114                 Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN
115             };
116 
117     private final Context mContext;
118     private final Looper mLooper;
119     private final UserManagerInternal mUserManagerInternal;
120     private final Object mLock = new Object();
121     private final AppOpsManager mAppOpsManager;
122     private final StatusBarManagerInternal mStatusBarManagerInternal;
123     final AtomicInteger mNextRouterOrManagerId = new AtomicInteger(1);
124     final ActivityManager mActivityManager;
125     final PowerManager mPowerManager;
126 
127     @GuardedBy("mLock")
128     private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
129     @GuardedBy("mLock")
130     private final ArrayMap<IBinder, RouterRecord> mAllRouterRecords = new ArrayMap<>();
131     @GuardedBy("mLock")
132     private final ArrayMap<IBinder, ManagerRecord> mAllManagerRecords = new ArrayMap<>();
133     @GuardedBy("mLock")
134     private int mCurrentActiveUserId = -1;
135 
136     private final ActivityManager.OnUidImportanceListener mOnUidImportanceListener =
137             (uid, importance) -> {
138                 synchronized (mLock) {
139                     final int count = mUserRecords.size();
140                     for (int i = 0; i < count; i++) {
141                         mUserRecords.valueAt(i).mHandler.maybeUpdateDiscoveryPreferenceForUid(uid);
142                     }
143                 }
144             };
145 
146     private final BroadcastReceiver mScreenOnOffReceiver = new BroadcastReceiver() {
147         @Override
148         public void onReceive(Context context, Intent intent) {
149             synchronized (mLock) {
150                 final int count = mUserRecords.size();
151                 for (int i = 0; i < count; i++) {
152                     UserHandler userHandler = mUserRecords.valueAt(i).mHandler;
153                     userHandler.sendMessage(PooledLambda.obtainMessage(
154                             UserHandler::updateDiscoveryPreferenceOnHandler, userHandler));
155                 }
156             }
157         }
158     };
159 
160     private final AppOpsManager.OnOpChangedListener mOnOpChangedListener =
161             new AppOpsManager.OnOpChangedListener() {
162                 @Override
163                 public void onOpChanged(String op, String packageName) {
164                     // Do nothing.
165                 }
166 
167                 @Override
168                 public void onOpChanged(
169                         @NonNull String op, @NonNull String packageName, int userId) {
170                     if (!TextUtils.equals(op, AppOpsManager.OPSTR_MEDIA_ROUTING_CONTROL)) {
171                         return;
172                     }
173                     synchronized (mLock) {
174                         revokeManagerRecordAccessIfNeededLocked(packageName, userId);
175                     }
176                 }
177             };
178 
179     @RequiresPermission(
180             allOf = {
181                 Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS,
182                 Manifest.permission.WATCH_APPOPS
183             })
MediaRouter2ServiceImpl(@onNull Context context, @NonNull Looper looper)184     /* package */ MediaRouter2ServiceImpl(@NonNull Context context, @NonNull Looper looper) {
185         mContext = context;
186         mLooper = looper;
187         mActivityManager = mContext.getSystemService(ActivityManager.class);
188         mActivityManager.addOnUidImportanceListener(mOnUidImportanceListener,
189                 REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING);
190         mPowerManager = mContext.getSystemService(PowerManager.class);
191         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
192         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
193         mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class);
194 
195         IntentFilter screenOnOffIntentFilter = new IntentFilter();
196         screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON);
197         screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF);
198         mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
199 
200         // Passing null package name to listen to all events.
201         mAppOpsManager.startWatchingMode(
202                 AppOpsManager.OP_MEDIA_ROUTING_CONTROL,
203                 /* packageName */ null,
204                 mOnOpChangedListener);
205 
206         mContext.getPackageManager().addOnPermissionsChangeListener(this::onPermissionsChanged);
207     }
208 
209     /**
210      * Called when there's a change in the permissions of an app.
211      *
212      * @param uid The uid of the app whose permissions changed.
213      */
onPermissionsChanged(int uid)214     private void onPermissionsChanged(int uid) {
215         synchronized (mLock) {
216             Optional<RouterRecord> affectedRouter =
217                     mAllRouterRecords.values().stream().filter(it -> it.mUid == uid).findFirst();
218             if (affectedRouter.isPresent()) {
219                 affectedRouter.get().maybeUpdateSystemRoutingPermissionLocked();
220             }
221         }
222     }
223 
224     // Start of methods that implement MediaRouter2 operations.
225 
226     @NonNull
getSystemRoutes(@onNull String callerPackageName, boolean isProxyRouter)227     public List<MediaRoute2Info> getSystemRoutes(@NonNull String callerPackageName,
228             boolean isProxyRouter) {
229         final int uid = Binder.getCallingUid();
230         final int pid = Binder.getCallingPid();
231         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
232 
233         boolean hasSystemRoutingPermissions;
234         if (!isProxyRouter) {
235             hasSystemRoutingPermissions = checkCallerHasSystemRoutingPermissions(pid, uid);
236         } else {
237             // Request from ProxyRouter.
238             hasSystemRoutingPermissions =
239                     checkCallerHasPrivilegedRoutingPermissions(pid, uid, callerPackageName);
240         }
241 
242         final long token = Binder.clearCallingIdentity();
243         try {
244             Collection<MediaRoute2Info> systemRoutes;
245             synchronized (mLock) {
246                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
247                 if (hasSystemRoutingPermissions) {
248                     MediaRoute2ProviderInfo providerInfo =
249                             userRecord.mHandler.mSystemProvider.getProviderInfo();
250                     if (providerInfo != null) {
251                         systemRoutes = providerInfo.getRoutes();
252                     } else {
253                         systemRoutes = Collections.emptyList();
254                     }
255                 } else {
256                     systemRoutes = new ArrayList<>();
257                     systemRoutes.add(userRecord.mHandler.mSystemProvider.getDefaultRoute());
258                 }
259             }
260             return new ArrayList<>(systemRoutes);
261         } finally {
262             Binder.restoreCallingIdentity(token);
263         }
264     }
265 
266     @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
showMediaOutputSwitcherWithRouter2(@onNull String packageName)267     public boolean showMediaOutputSwitcherWithRouter2(@NonNull String packageName) {
268         UserHandle userHandle = Binder.getCallingUserHandle();
269         final long token = Binder.clearCallingIdentity();
270         try {
271             return showOutputSwitcher(packageName, userHandle);
272         } finally {
273             Binder.restoreCallingIdentity(token);
274         }
275     }
276 
registerRouter2(@onNull IMediaRouter2 router, @NonNull String packageName)277     public void registerRouter2(@NonNull IMediaRouter2 router, @NonNull String packageName) {
278         Objects.requireNonNull(router, "router must not be null");
279         if (TextUtils.isEmpty(packageName)) {
280             throw new IllegalArgumentException("packageName must not be empty");
281         }
282 
283         final int uid = Binder.getCallingUid();
284         final int pid = Binder.getCallingPid();
285         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
286         final boolean hasConfigureWifiDisplayPermission =
287                 mContext.checkCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY)
288                         == PackageManager.PERMISSION_GRANTED;
289         final boolean hasModifyAudioRoutingPermission =
290                 checkCallerHasModifyAudioRoutingPermission(pid, uid);
291         boolean hasMediaContentControlPermission = checkMediaContentControlPermission(uid, pid);
292         boolean hasMediaRoutingControlPermission =
293                 checkMediaRoutingControlPermission(uid, pid, packageName);
294 
295         final long token = Binder.clearCallingIdentity();
296         try {
297             synchronized (mLock) {
298                 registerRouter2Locked(
299                         router,
300                         uid,
301                         pid,
302                         packageName,
303                         userId,
304                         hasConfigureWifiDisplayPermission,
305                         hasModifyAudioRoutingPermission,
306                         hasMediaContentControlPermission,
307                         hasMediaRoutingControlPermission);
308             }
309         } finally {
310             Binder.restoreCallingIdentity(token);
311         }
312     }
313 
unregisterRouter2(@onNull IMediaRouter2 router)314     public void unregisterRouter2(@NonNull IMediaRouter2 router) {
315         Objects.requireNonNull(router, "router must not be null");
316 
317         final long token = Binder.clearCallingIdentity();
318         try {
319             synchronized (mLock) {
320                 unregisterRouter2Locked(router, false);
321             }
322         } finally {
323             Binder.restoreCallingIdentity(token);
324         }
325     }
326 
327     @RequiresPermission(
328             anyOf = {
329                 Manifest.permission.MEDIA_ROUTING_CONTROL,
330                 Manifest.permission.MEDIA_CONTENT_CONTROL
331             },
332             conditional = true)
updateScanningState( @onNull IMediaRouter2 router, @ScanningState int scanningState)333     public void updateScanningState(
334             @NonNull IMediaRouter2 router, @ScanningState int scanningState) {
335         Objects.requireNonNull(router, "router must not be null");
336         validateScanningStateValue(scanningState);
337 
338         final long token = Binder.clearCallingIdentity();
339         try {
340             synchronized (mLock) {
341                 updateScanningStateLocked(router, scanningState);
342             }
343         } finally {
344             Binder.restoreCallingIdentity(token);
345         }
346     }
347 
setDiscoveryRequestWithRouter2(@onNull IMediaRouter2 router, @NonNull RouteDiscoveryPreference preference)348     public void setDiscoveryRequestWithRouter2(@NonNull IMediaRouter2 router,
349             @NonNull RouteDiscoveryPreference preference) {
350         Objects.requireNonNull(router, "router must not be null");
351         Objects.requireNonNull(preference, "preference must not be null");
352 
353         final long token = Binder.clearCallingIdentity();
354         try {
355             synchronized (mLock) {
356                 RouterRecord routerRecord = mAllRouterRecords.get(router.asBinder());
357                 if (routerRecord == null) {
358                     Slog.w(TAG, "Ignoring updating discoveryRequest of null routerRecord.");
359                     return;
360                 }
361                 setDiscoveryRequestWithRouter2Locked(routerRecord, preference);
362             }
363         } finally {
364             Binder.restoreCallingIdentity(token);
365         }
366     }
367 
setRouteListingPreference( @onNull IMediaRouter2 router, @Nullable RouteListingPreference routeListingPreference)368     public void setRouteListingPreference(
369             @NonNull IMediaRouter2 router,
370             @Nullable RouteListingPreference routeListingPreference) {
371         ComponentName linkedItemLandingComponent =
372                 routeListingPreference != null
373                         ? routeListingPreference.getLinkedItemComponentName()
374                         : null;
375         if (linkedItemLandingComponent != null) {
376             int callingUid = Binder.getCallingUid();
377             MediaServerUtils.enforcePackageName(
378                     mContext, linkedItemLandingComponent.getPackageName(), callingUid);
379             if (!MediaServerUtils.isValidActivityComponentName(
380                     mContext,
381                     linkedItemLandingComponent,
382                     RouteListingPreference.ACTION_TRANSFER_MEDIA,
383                     Binder.getCallingUserHandle())) {
384                 throw new IllegalArgumentException(
385                         "Unable to resolve "
386                                 + linkedItemLandingComponent
387                                 + " to a valid activity for "
388                                 + RouteListingPreference.ACTION_TRANSFER_MEDIA);
389             }
390         }
391 
392         final long token = Binder.clearCallingIdentity();
393         try {
394             synchronized (mLock) {
395                 RouterRecord routerRecord = mAllRouterRecords.get(router.asBinder());
396                 if (routerRecord == null) {
397                     Slog.w(TAG, "Ignoring updating route listing of null routerRecord.");
398                     return;
399                 }
400                 setRouteListingPreferenceLocked(routerRecord, routeListingPreference);
401             }
402         } finally {
403             Binder.restoreCallingIdentity(token);
404         }
405     }
406 
setRouteVolumeWithRouter2(@onNull IMediaRouter2 router, @NonNull MediaRoute2Info route, int volume)407     public void setRouteVolumeWithRouter2(@NonNull IMediaRouter2 router,
408             @NonNull MediaRoute2Info route, int volume) {
409         Objects.requireNonNull(router, "router must not be null");
410         Objects.requireNonNull(route, "route must not be null");
411 
412         final long token = Binder.clearCallingIdentity();
413         try {
414             synchronized (mLock) {
415                 setRouteVolumeWithRouter2Locked(router, route, volume);
416             }
417         } finally {
418             Binder.restoreCallingIdentity(token);
419         }
420     }
421 
requestCreateSessionWithRouter2( @onNull IMediaRouter2 router, int requestId, long managerRequestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, Bundle sessionHints)422     public void requestCreateSessionWithRouter2(
423             @NonNull IMediaRouter2 router,
424             int requestId,
425             long managerRequestId,
426             @NonNull RoutingSessionInfo oldSession,
427             @NonNull MediaRoute2Info route,
428             Bundle sessionHints) {
429         Objects.requireNonNull(router, "router must not be null");
430         Objects.requireNonNull(oldSession, "oldSession must not be null");
431         Objects.requireNonNull(route, "route must not be null");
432 
433         final long token = Binder.clearCallingIdentity();
434         try {
435             synchronized (mLock) {
436                 requestCreateSessionWithRouter2Locked(
437                         requestId,
438                         managerRequestId,
439                         router,
440                         oldSession,
441                         route,
442                         sessionHints);
443             }
444         } finally {
445             Binder.restoreCallingIdentity(token);
446         }
447     }
448 
selectRouteWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)449     public void selectRouteWithRouter2(@NonNull IMediaRouter2 router,
450             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
451         Objects.requireNonNull(router, "router must not be null");
452         Objects.requireNonNull(route, "route must not be null");
453         if (TextUtils.isEmpty(uniqueSessionId)) {
454             throw new IllegalArgumentException("uniqueSessionId must not be empty");
455         }
456 
457         final long token = Binder.clearCallingIdentity();
458         try {
459             synchronized (mLock) {
460                 selectRouteWithRouter2Locked(router, uniqueSessionId, route);
461             }
462         } finally {
463             Binder.restoreCallingIdentity(token);
464         }
465     }
466 
deselectRouteWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)467     public void deselectRouteWithRouter2(@NonNull IMediaRouter2 router,
468             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
469         Objects.requireNonNull(router, "router must not be null");
470         Objects.requireNonNull(route, "route must not be null");
471         if (TextUtils.isEmpty(uniqueSessionId)) {
472             throw new IllegalArgumentException("uniqueSessionId must not be empty");
473         }
474 
475         final long token = Binder.clearCallingIdentity();
476         try {
477             synchronized (mLock) {
478                 deselectRouteWithRouter2Locked(router, uniqueSessionId, route);
479             }
480         } finally {
481             Binder.restoreCallingIdentity(token);
482         }
483     }
484 
transferToRouteWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)485     public void transferToRouteWithRouter2(@NonNull IMediaRouter2 router,
486             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
487         Objects.requireNonNull(router, "router must not be null");
488         Objects.requireNonNull(route, "route must not be null");
489         if (TextUtils.isEmpty(uniqueSessionId)) {
490             throw new IllegalArgumentException("uniqueSessionId must not be empty");
491         }
492 
493         UserHandle userHandle = Binder.getCallingUserHandle();
494         final long token = Binder.clearCallingIdentity();
495         try {
496             synchronized (mLock) {
497                 transferToRouteWithRouter2Locked(router, userHandle, uniqueSessionId, route);
498             }
499         } finally {
500             Binder.restoreCallingIdentity(token);
501         }
502     }
503 
setSessionVolumeWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, int volume)504     public void setSessionVolumeWithRouter2(@NonNull IMediaRouter2 router,
505             @NonNull String uniqueSessionId, int volume) {
506         Objects.requireNonNull(router, "router must not be null");
507         Objects.requireNonNull(uniqueSessionId, "uniqueSessionId must not be null");
508         if (TextUtils.isEmpty(uniqueSessionId)) {
509             throw new IllegalArgumentException("uniqueSessionId must not be empty");
510         }
511 
512         final long token = Binder.clearCallingIdentity();
513         try {
514             synchronized (mLock) {
515                 setSessionVolumeWithRouter2Locked(router, uniqueSessionId, volume);
516             }
517         } finally {
518             Binder.restoreCallingIdentity(token);
519         }
520     }
521 
releaseSessionWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId)522     public void releaseSessionWithRouter2(@NonNull IMediaRouter2 router,
523             @NonNull String uniqueSessionId) {
524         Objects.requireNonNull(router, "router must not be null");
525         if (TextUtils.isEmpty(uniqueSessionId)) {
526             throw new IllegalArgumentException("uniqueSessionId must not be empty");
527         }
528 
529         final long token = Binder.clearCallingIdentity();
530         try {
531             synchronized (mLock) {
532                 releaseSessionWithRouter2Locked(router, uniqueSessionId);
533             }
534         } finally {
535             Binder.restoreCallingIdentity(token);
536         }
537     }
538 
539     // End of methods that implement MediaRouter2 operations.
540 
541     // Start of methods that implement MediaRouter2Manager operations.
542 
543     @NonNull
getRemoteSessions(@onNull IMediaRouter2Manager manager)544     public List<RoutingSessionInfo> getRemoteSessions(@NonNull IMediaRouter2Manager manager) {
545         Objects.requireNonNull(manager, "manager must not be null");
546         final long token = Binder.clearCallingIdentity();
547         try {
548             synchronized (mLock) {
549                 return getRemoteSessionsLocked(manager);
550             }
551         } finally {
552             Binder.restoreCallingIdentity(token);
553         }
554     }
555 
556     @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
registerManager(@onNull IMediaRouter2Manager manager, @NonNull String callerPackageName)557     public void registerManager(@NonNull IMediaRouter2Manager manager,
558             @NonNull String callerPackageName) {
559         Objects.requireNonNull(manager, "manager must not be null");
560         if (TextUtils.isEmpty(callerPackageName)) {
561             throw new IllegalArgumentException("callerPackageName must not be empty");
562         }
563 
564         final int callerUid = Binder.getCallingUid();
565         final int callerPid = Binder.getCallingPid();
566         final UserHandle callerUser = Binder.getCallingUserHandle();
567 
568         enforcePrivilegedRoutingPermissions(callerUid, callerPid, callerPackageName);
569 
570         final long token = Binder.clearCallingIdentity();
571         try {
572             synchronized (mLock) {
573                 registerManagerLocked(
574                         manager,
575                         callerUid,
576                         callerPid,
577                         callerPackageName,
578                         /* targetPackageName */ null,
579                         callerUser);
580             }
581         } finally {
582             Binder.restoreCallingIdentity(token);
583         }
584     }
585 
586     @RequiresPermission(
587             anyOf = {
588                 Manifest.permission.MEDIA_CONTENT_CONTROL,
589                 Manifest.permission.MEDIA_ROUTING_CONTROL
590             })
registerProxyRouter( @onNull IMediaRouter2Manager manager, @NonNull String callerPackageName, @NonNull String targetPackageName, @NonNull UserHandle targetUser)591     public void registerProxyRouter(
592             @NonNull IMediaRouter2Manager manager,
593             @NonNull String callerPackageName,
594             @NonNull String targetPackageName,
595             @NonNull UserHandle targetUser) {
596         Objects.requireNonNull(manager, "manager must not be null");
597         Objects.requireNonNull(targetUser, "targetUser must not be null");
598 
599         if (TextUtils.isEmpty(targetPackageName)) {
600             throw new IllegalArgumentException("targetPackageName must not be empty");
601         }
602 
603         int callerUid = Binder.getCallingUid();
604         int callerPid = Binder.getCallingPid();
605         final long token = Binder.clearCallingIdentity();
606 
607         try {
608             enforcePrivilegedRoutingPermissions(callerUid, callerPid, callerPackageName);
609             enforceCrossUserPermissions(callerUid, callerPid, targetUser);
610             if (!verifyPackageExistsForUser(targetPackageName, targetUser)) {
611                 throw new IllegalArgumentException(
612                         "targetPackageName does not exist: " + targetPackageName);
613             }
614 
615             synchronized (mLock) {
616                 registerManagerLocked(
617                         manager,
618                         callerUid,
619                         callerPid,
620                         callerPackageName,
621                         targetPackageName,
622                         targetUser);
623             }
624         } finally {
625             Binder.restoreCallingIdentity(token);
626         }
627     }
628 
unregisterManager(@onNull IMediaRouter2Manager manager)629     public void unregisterManager(@NonNull IMediaRouter2Manager manager) {
630         Objects.requireNonNull(manager, "manager must not be null");
631 
632         final long token = Binder.clearCallingIdentity();
633         try {
634             synchronized (mLock) {
635                 unregisterManagerLocked(manager, false);
636             }
637         } finally {
638             Binder.restoreCallingIdentity(token);
639         }
640     }
641 
updateScanningState( @onNull IMediaRouter2Manager manager, @ScanningState int scanningState)642     public void updateScanningState(
643             @NonNull IMediaRouter2Manager manager, @ScanningState int scanningState) {
644         Objects.requireNonNull(manager, "manager must not be null");
645         validateScanningStateValue(scanningState);
646 
647         final long token = Binder.clearCallingIdentity();
648         try {
649             synchronized (mLock) {
650                 updateScanningStateLocked(manager, scanningState);
651             }
652         } finally {
653             Binder.restoreCallingIdentity(token);
654         }
655     }
656 
setRouteVolumeWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull MediaRoute2Info route, int volume)657     public void setRouteVolumeWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
658             @NonNull MediaRoute2Info route, int volume) {
659         Objects.requireNonNull(manager, "manager must not be null");
660         Objects.requireNonNull(route, "route must not be null");
661 
662         final long token = Binder.clearCallingIdentity();
663         try {
664             synchronized (mLock) {
665                 setRouteVolumeWithManagerLocked(requestId, manager, route, volume);
666             }
667         } finally {
668             Binder.restoreCallingIdentity(token);
669         }
670     }
671 
requestCreateSessionWithManager( @onNull IMediaRouter2Manager manager, int requestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName)672     public void requestCreateSessionWithManager(
673             @NonNull IMediaRouter2Manager manager,
674             int requestId,
675             @NonNull RoutingSessionInfo oldSession,
676             @NonNull MediaRoute2Info route,
677             @NonNull UserHandle transferInitiatorUserHandle,
678             @NonNull String transferInitiatorPackageName) {
679         Objects.requireNonNull(manager, "manager must not be null");
680         Objects.requireNonNull(oldSession, "oldSession must not be null");
681         Objects.requireNonNull(route, "route must not be null");
682         Objects.requireNonNull(transferInitiatorUserHandle);
683 
684         final long token = Binder.clearCallingIdentity();
685         try {
686             synchronized (mLock) {
687                 requestCreateSessionWithManagerLocked(
688                         requestId,
689                         manager,
690                         oldSession,
691                         route,
692                         transferInitiatorUserHandle,
693                         transferInitiatorPackageName);
694             }
695         } finally {
696             Binder.restoreCallingIdentity(token);
697         }
698     }
699 
selectRouteWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)700     public void selectRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
701             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
702         Objects.requireNonNull(manager, "manager must not be null");
703         if (TextUtils.isEmpty(uniqueSessionId)) {
704             throw new IllegalArgumentException("uniqueSessionId must not be empty");
705         }
706         Objects.requireNonNull(route, "route must not be null");
707 
708         final long token = Binder.clearCallingIdentity();
709         try {
710             synchronized (mLock) {
711                 selectRouteWithManagerLocked(requestId, manager, uniqueSessionId, route);
712             }
713         } finally {
714             Binder.restoreCallingIdentity(token);
715         }
716     }
717 
deselectRouteWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)718     public void deselectRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
719             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
720         Objects.requireNonNull(manager, "manager must not be null");
721         if (TextUtils.isEmpty(uniqueSessionId)) {
722             throw new IllegalArgumentException("uniqueSessionId must not be empty");
723         }
724         Objects.requireNonNull(route, "route must not be null");
725 
726         final long token = Binder.clearCallingIdentity();
727         try {
728             synchronized (mLock) {
729                 deselectRouteWithManagerLocked(requestId, manager, uniqueSessionId, route);
730             }
731         } finally {
732             Binder.restoreCallingIdentity(token);
733         }
734     }
735 
transferToRouteWithManager( @onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName)736     public void transferToRouteWithManager(
737             @NonNull IMediaRouter2Manager manager,
738             int requestId,
739             @NonNull String uniqueSessionId,
740             @NonNull MediaRoute2Info route,
741             @NonNull UserHandle transferInitiatorUserHandle,
742             @NonNull String transferInitiatorPackageName) {
743         Objects.requireNonNull(manager, "manager must not be null");
744         if (TextUtils.isEmpty(uniqueSessionId)) {
745             throw new IllegalArgumentException("uniqueSessionId must not be empty");
746         }
747         Objects.requireNonNull(route, "route must not be null");
748         Objects.requireNonNull(transferInitiatorUserHandle);
749         Objects.requireNonNull(transferInitiatorPackageName);
750 
751         final long token = Binder.clearCallingIdentity();
752         try {
753             synchronized (mLock) {
754                 transferToRouteWithManagerLocked(
755                         requestId,
756                         manager,
757                         uniqueSessionId,
758                         route,
759                         RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST,
760                         transferInitiatorUserHandle,
761                         transferInitiatorPackageName);
762             }
763         } finally {
764             Binder.restoreCallingIdentity(token);
765         }
766     }
767 
setSessionVolumeWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId, int volume)768     public void setSessionVolumeWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
769             @NonNull String uniqueSessionId, int volume) {
770         Objects.requireNonNull(manager, "manager must not be null");
771         if (TextUtils.isEmpty(uniqueSessionId)) {
772             throw new IllegalArgumentException("uniqueSessionId must not be empty");
773         }
774 
775         final long token = Binder.clearCallingIdentity();
776         try {
777             synchronized (mLock) {
778                 setSessionVolumeWithManagerLocked(requestId, manager, uniqueSessionId, volume);
779             }
780         } finally {
781             Binder.restoreCallingIdentity(token);
782         }
783     }
784 
releaseSessionWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId)785     public void releaseSessionWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
786             @NonNull String uniqueSessionId) {
787         Objects.requireNonNull(manager, "manager must not be null");
788         if (TextUtils.isEmpty(uniqueSessionId)) {
789             throw new IllegalArgumentException("uniqueSessionId must not be empty");
790         }
791 
792         final long token = Binder.clearCallingIdentity();
793         try {
794             synchronized (mLock) {
795                 releaseSessionWithManagerLocked(requestId, manager, uniqueSessionId);
796             }
797         } finally {
798             Binder.restoreCallingIdentity(token);
799         }
800     }
801 
802     @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
showMediaOutputSwitcherWithProxyRouter( @onNull IMediaRouter2Manager proxyRouter)803     public boolean showMediaOutputSwitcherWithProxyRouter(
804             @NonNull IMediaRouter2Manager proxyRouter) {
805         Objects.requireNonNull(proxyRouter, "Proxy router must not be null");
806 
807         final long token = Binder.clearCallingIdentity();
808         try {
809             synchronized (mLock) {
810                 final IBinder binder = proxyRouter.asBinder();
811                 ManagerRecord proxyRouterRecord = mAllManagerRecords.get(binder);
812 
813                 if (proxyRouterRecord.mTargetPackageName == null) {
814                     throw new UnsupportedOperationException(
815                             "Only proxy routers can show the Output Switcher.");
816                 }
817 
818                 return showOutputSwitcher(
819                         proxyRouterRecord.mTargetPackageName,
820                         UserHandle.of(proxyRouterRecord.mUserRecord.mUserId));
821             }
822         } finally {
823             Binder.restoreCallingIdentity(token);
824         }
825     }
826 
827     // End of methods that implement MediaRouter2Manager operations.
828 
829     // Start of methods that implements operations for both MediaRouter2 and MediaRouter2Manager.
830 
831     @Nullable
getSystemSessionInfo( @onNull String callerPackageName, @Nullable String targetPackageName, boolean setDeviceRouteSelected)832     public RoutingSessionInfo getSystemSessionInfo(
833             @NonNull String callerPackageName,
834             @Nullable String targetPackageName,
835             boolean setDeviceRouteSelected) {
836         final int uid = Binder.getCallingUid();
837         final int pid = Binder.getCallingPid();
838         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
839 
840         boolean hasSystemRoutingPermissions;
841         if (targetPackageName == null) {
842             hasSystemRoutingPermissions = checkCallerHasSystemRoutingPermissions(pid, uid);
843         } else {
844             // Request from ProxyRouter.
845             hasSystemRoutingPermissions =
846                     checkCallerHasPrivilegedRoutingPermissions(pid, uid, callerPackageName);
847         }
848 
849         final long token = Binder.clearCallingIdentity();
850         try {
851             synchronized (mLock) {
852                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
853                 List<RoutingSessionInfo> sessionInfos;
854                 if (hasSystemRoutingPermissions) {
855                     if (setDeviceRouteSelected) {
856                         // Return a fake system session that shows the device route as selected and
857                         // available bluetooth routes as transferable.
858                         return userRecord.mHandler.mSystemProvider
859                                 .generateDeviceRouteSelectedSessionInfo(targetPackageName);
860                     } else {
861                         sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
862                         if (!sessionInfos.isEmpty()) {
863                             // Return a copy of the current system session with no modification,
864                             // except setting the client package name.
865                             return new RoutingSessionInfo.Builder(sessionInfos.get(0))
866                                     .setClientPackageName(targetPackageName)
867                                     .build();
868                         } else {
869                             Slog.w(TAG, "System provider does not have any session info.");
870                         }
871                     }
872                 } else {
873                     return new RoutingSessionInfo.Builder(
874                                     userRecord.mHandler.mSystemProvider.getDefaultSessionInfo())
875                             .setClientPackageName(targetPackageName)
876                             .build();
877                 }
878             }
879             return null;
880         } finally {
881             Binder.restoreCallingIdentity(token);
882         }
883     }
884 
checkCallerHasSystemRoutingPermissions(int pid, int uid)885     private boolean checkCallerHasSystemRoutingPermissions(int pid, int uid) {
886         return checkCallerHasModifyAudioRoutingPermission(pid, uid)
887                 || checkCallerHasBluetoothPermissions(pid, uid);
888     }
889 
checkCallerHasPrivilegedRoutingPermissions( int pid, int uid, @NonNull String callerPackageName)890     private boolean checkCallerHasPrivilegedRoutingPermissions(
891             int pid, int uid, @NonNull String callerPackageName) {
892         return checkMediaContentControlPermission(uid, pid)
893                 || checkMediaRoutingControlPermission(uid, pid, callerPackageName);
894     }
895 
checkCallerHasModifyAudioRoutingPermission(int pid, int uid)896     private boolean checkCallerHasModifyAudioRoutingPermission(int pid, int uid) {
897         return mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_ROUTING, pid, uid)
898                 == PackageManager.PERMISSION_GRANTED;
899     }
900 
checkCallerHasBluetoothPermissions(int pid, int uid)901     private boolean checkCallerHasBluetoothPermissions(int pid, int uid) {
902         boolean hasBluetoothRoutingPermission = true;
903         for (String permission : BLUETOOTH_PERMISSIONS_FOR_SYSTEM_ROUTING) {
904             hasBluetoothRoutingPermission &=
905                     mContext.checkPermission(permission, pid, uid)
906                             == PackageManager.PERMISSION_GRANTED;
907         }
908         return hasBluetoothRoutingPermission;
909     }
910 
911     @RequiresPermission(
912             anyOf = {
913                 Manifest.permission.MEDIA_ROUTING_CONTROL,
914                 Manifest.permission.MEDIA_CONTENT_CONTROL
915             })
enforcePrivilegedRoutingPermissions( int callerUid, int callerPid, @NonNull String callerPackageName)916     private void enforcePrivilegedRoutingPermissions(
917             int callerUid, int callerPid, @NonNull String callerPackageName) {
918         if (checkMediaContentControlPermission(callerUid, callerPid)) {
919             return;
920         }
921 
922         if (!checkMediaRoutingControlPermission(callerUid, callerPid, callerPackageName)) {
923             throw new SecurityException(
924                     "Must hold MEDIA_CONTENT_CONTROL or MEDIA_ROUTING_CONTROL permissions.");
925         }
926     }
927 
checkMediaContentControlPermission(int callerUid, int callerPid)928     private boolean checkMediaContentControlPermission(int callerUid, int callerPid) {
929         return mContext.checkPermission(
930                         Manifest.permission.MEDIA_CONTENT_CONTROL, callerPid, callerUid)
931                 == PackageManager.PERMISSION_GRANTED;
932     }
933 
checkMediaRoutingControlPermission( int callerUid, int callerPid, @NonNull String callerPackageName)934     private boolean checkMediaRoutingControlPermission(
935             int callerUid, int callerPid, @NonNull String callerPackageName) {
936         if (!Flags.enablePrivilegedRoutingForMediaRoutingControl()) {
937             return false;
938         }
939 
940         return PermissionChecker.checkPermissionForDataDelivery(
941                         mContext,
942                         Manifest.permission.MEDIA_ROUTING_CONTROL,
943                         callerPid,
944                         callerUid,
945                         callerPackageName,
946                         /* attributionTag */ null,
947                         /* message */ "Checking permissions for registering manager in"
948                                 + " MediaRouter2ServiceImpl.")
949                 == PermissionChecker.PERMISSION_GRANTED;
950     }
951 
952     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS)
verifyPackageExistsForUser( @onNull String clientPackageName, @NonNull UserHandle user)953     private boolean verifyPackageExistsForUser(
954             @NonNull String clientPackageName, @NonNull UserHandle user) {
955         try {
956             PackageManager pm = mContext.getPackageManager();
957             pm.getPackageInfoAsUser(
958                     clientPackageName, PackageManager.PackageInfoFlags.of(0), user.getIdentifier());
959             return true;
960         } catch (PackageManager.NameNotFoundException ex) {
961             return false;
962         }
963     }
964 
965     /**
966      * Enforces the caller has {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} if the
967      * caller's user is different from the target user.
968      */
enforceCrossUserPermissions( int callerUid, int callerPid, @NonNull UserHandle targetUser)969     private void enforceCrossUserPermissions(
970             int callerUid, int callerPid, @NonNull UserHandle targetUser) {
971         int callerUserId = UserHandle.getUserId(callerUid);
972 
973         if (targetUser.getIdentifier() != callerUserId) {
974             mContext.enforcePermission(
975                     Manifest.permission.INTERACT_ACROSS_USERS_FULL,
976                     callerPid,
977                     callerUid,
978                     "Must hold INTERACT_ACROSS_USERS_FULL to control an app in a different"
979                             + " userId.");
980         }
981     }
982 
983     @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
showOutputSwitcher( @onNull String packageName, @NonNull UserHandle userHandle)984     private boolean showOutputSwitcher(
985             @NonNull String packageName, @NonNull UserHandle userHandle) {
986         if (mActivityManager.getPackageImportance(packageName) > IMPORTANCE_FOREGROUND) {
987             Slog.w(TAG, "showMediaOutputSwitcher only works when called from foreground");
988             return false;
989         }
990         synchronized (mLock) {
991             mStatusBarManagerInternal.showMediaOutputSwitcher(packageName, userHandle);
992         }
993         return true;
994     }
995 
996     // End of methods that implements operations for both MediaRouter2 and MediaRouter2Manager.
997 
dump(@onNull PrintWriter pw, @NonNull String prefix)998     public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
999         pw.println(prefix + "MediaRouter2ServiceImpl");
1000 
1001         String indent = prefix + "  ";
1002 
1003         synchronized (mLock) {
1004             pw.println(indent + "mNextRouterOrManagerId=" + mNextRouterOrManagerId.get());
1005             pw.println(indent + "mCurrentActiveUserId=" + mCurrentActiveUserId);
1006 
1007             pw.println(indent + "UserRecords:");
1008             if (mUserRecords.size() > 0) {
1009                 for (int i = 0; i < mUserRecords.size(); i++) {
1010                     mUserRecords.valueAt(i).dump(pw, indent + "  ");
1011                 }
1012             } else {
1013                 pw.println(indent + "  <no user records>");
1014             }
1015         }
1016     }
1017 
updateRunningUserAndProfiles(int newActiveUserId)1018     /* package */ void updateRunningUserAndProfiles(int newActiveUserId) {
1019         synchronized (mLock) {
1020             if (mCurrentActiveUserId != newActiveUserId) {
1021                 Slog.i(TAG, TextUtils.formatSimple(
1022                         "switchUser | user: %d", newActiveUserId));
1023 
1024                 mCurrentActiveUserId = newActiveUserId;
1025                 // disposeUserIfNeededLocked might modify the collection, hence clone
1026                 final var userRecords = mUserRecords.clone();
1027                 for (int i = 0; i < userRecords.size(); i++) {
1028                     int userId = userRecords.keyAt(i);
1029                     UserRecord userRecord = userRecords.valueAt(i);
1030                     if (isUserActiveLocked(userId)) {
1031                         // userId corresponds to the active user, or one of its profiles. We
1032                         // ensure the associated structures are initialized.
1033                         userRecord.mHandler.sendMessage(
1034                                 obtainMessage(UserHandler::start, userRecord.mHandler));
1035                     } else {
1036                         userRecord.mHandler.sendMessage(
1037                                 obtainMessage(UserHandler::stop, userRecord.mHandler));
1038                         disposeUserIfNeededLocked(userRecord);
1039                     }
1040                 }
1041             }
1042         }
1043     }
1044 
routerDied(@onNull RouterRecord routerRecord)1045     void routerDied(@NonNull RouterRecord routerRecord) {
1046         synchronized (mLock) {
1047             unregisterRouter2Locked(routerRecord.mRouter, true);
1048         }
1049     }
1050 
managerDied(@onNull ManagerRecord managerRecord)1051     void managerDied(@NonNull ManagerRecord managerRecord) {
1052         synchronized (mLock) {
1053             unregisterManagerLocked(managerRecord.mManager, true);
1054         }
1055     }
1056 
1057     /**
1058      * Returns {@code true} if the given {@code userId} corresponds to the active user or a profile
1059      * of the active user, returns {@code false} otherwise.
1060      */
1061     @GuardedBy("mLock")
isUserActiveLocked(int userId)1062     private boolean isUserActiveLocked(int userId) {
1063         return mUserManagerInternal.getProfileParentId(userId) == mCurrentActiveUserId;
1064     }
1065 
1066     @GuardedBy("mLock")
revokeManagerRecordAccessIfNeededLocked(@onNull String packageName, int userId)1067     private void revokeManagerRecordAccessIfNeededLocked(@NonNull String packageName, int userId) {
1068         UserRecord userRecord = mUserRecords.get(userId);
1069         if (userRecord == null) {
1070             return;
1071         }
1072 
1073         List<ManagerRecord> managers =
1074                 userRecord.mManagerRecords.stream()
1075                         .filter(r -> !r.mHasMediaContentControl)
1076                         .filter(r -> TextUtils.equals(r.mOwnerPackageName, packageName))
1077                         .collect(Collectors.toList());
1078 
1079         if (managers.isEmpty()) {
1080             return;
1081         }
1082 
1083         ManagerRecord record = managers.getFirst();
1084 
1085         // Uid and package name are shared across all manager records in the list.
1086         boolean isAppOpAllowed =
1087                 mAppOpsManager.unsafeCheckOpNoThrow(
1088                                 AppOpsManager.OPSTR_MEDIA_ROUTING_CONTROL,
1089                                 record.mOwnerUid,
1090                                 record.mOwnerPackageName)
1091                         == AppOpsManager.MODE_ALLOWED;
1092 
1093         if (isAppOpAllowed) {
1094             return;
1095         }
1096 
1097         for (ManagerRecord manager : managers) {
1098             boolean isRegularPermission =
1099                     mContext.checkPermission(
1100                                     Manifest.permission.MEDIA_ROUTING_CONTROL,
1101                                     manager.mOwnerPid,
1102                                     manager.mOwnerUid)
1103                             == PackageManager.PERMISSION_GRANTED;
1104 
1105             if (isRegularPermission) {
1106                 // We should check the regular permission for all manager records, as different PIDs
1107                 // might yield different permission results.
1108                 continue;
1109             }
1110 
1111             Log.w(
1112                     TAG,
1113                     TextUtils.formatSimple(
1114                             "Revoking access to manager record id: %d, package: %s, userId:"
1115                                     + " %d",
1116                             manager.mManagerId, manager.mOwnerPackageName, userRecord.mUserId));
1117 
1118             unregisterManagerLocked(manager.mManager, /* died */ false);
1119 
1120             try {
1121                 manager.mManager.invalidateInstance();
1122             } catch (RemoteException ex) {
1123                 Slog.w(TAG, "Failed to notify manager= " + manager + " of permission revocation.");
1124             }
1125         }
1126     }
1127 
1128     // Start of locked methods that are used by MediaRouter2.
1129 
1130     @GuardedBy("mLock")
registerRouter2Locked( @onNull IMediaRouter2 router, int uid, int pid, @NonNull String packageName, int userId, boolean hasConfigureWifiDisplayPermission, boolean hasModifyAudioRoutingPermission, boolean hasMediaContentControlPermission, boolean hasMediaRoutingControlPermission)1131     private void registerRouter2Locked(
1132             @NonNull IMediaRouter2 router,
1133             int uid,
1134             int pid,
1135             @NonNull String packageName,
1136             int userId,
1137             boolean hasConfigureWifiDisplayPermission,
1138             boolean hasModifyAudioRoutingPermission,
1139             boolean hasMediaContentControlPermission,
1140             boolean hasMediaRoutingControlPermission) {
1141         final IBinder binder = router.asBinder();
1142         if (mAllRouterRecords.get(binder) != null) {
1143             Slog.w(TAG, "registerRouter2Locked: Same router already exists. packageName="
1144                     + packageName);
1145             return;
1146         }
1147 
1148         UserRecord userRecord = getOrCreateUserRecordLocked(userId);
1149         RouterRecord routerRecord =
1150                 new RouterRecord(
1151                         userRecord,
1152                         router,
1153                         uid,
1154                         pid,
1155                         packageName,
1156                         hasConfigureWifiDisplayPermission,
1157                         hasModifyAudioRoutingPermission,
1158                         hasMediaContentControlPermission,
1159                         hasMediaRoutingControlPermission);
1160         try {
1161             binder.linkToDeath(routerRecord, 0);
1162         } catch (RemoteException ex) {
1163             throw new RuntimeException("MediaRouter2 died prematurely.", ex);
1164         }
1165 
1166         userRecord.mRouterRecords.add(routerRecord);
1167         mAllRouterRecords.put(binder, routerRecord);
1168 
1169         userRecord.mHandler.sendMessage(
1170                 obtainMessage(UserHandler::notifyRouterRegistered,
1171                         userRecord.mHandler, routerRecord));
1172 
1173         Slog.i(
1174                 TAG,
1175                 TextUtils.formatSimple(
1176                         "registerRouter2 | package: %s, uid: %d, pid: %d, router id: %d,"
1177                                 + " hasMediaRoutingControl: %b",
1178                         packageName,
1179                         uid,
1180                         pid,
1181                         routerRecord.mRouterId,
1182                         hasMediaRoutingControlPermission));
1183     }
1184 
1185     @GuardedBy("mLock")
unregisterRouter2Locked(@onNull IMediaRouter2 router, boolean died)1186     private void unregisterRouter2Locked(@NonNull IMediaRouter2 router, boolean died) {
1187         RouterRecord routerRecord = mAllRouterRecords.remove(router.asBinder());
1188         if (routerRecord == null) {
1189             Slog.w(
1190                     TAG,
1191                     TextUtils.formatSimple(
1192                             "Ignoring unregistering unknown router: %s, died: %b", router, died));
1193             return;
1194         }
1195 
1196         Slog.i(
1197                 TAG,
1198                 TextUtils.formatSimple(
1199                         "unregisterRouter2 | package: %s, router id: %d, died: %b",
1200                         routerRecord.mPackageName, routerRecord.mRouterId, died));
1201 
1202         UserRecord userRecord = routerRecord.mUserRecord;
1203         userRecord.mRouterRecords.remove(routerRecord);
1204         routerRecord.mUserRecord.mHandler.sendMessage(
1205                 obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
1206                         routerRecord.mUserRecord.mHandler,
1207                         routerRecord.mPackageName, null));
1208         routerRecord.mUserRecord.mHandler.sendMessage(
1209                 obtainMessage(
1210                         UserHandler::notifyRouteListingPreferenceChangeToManagers,
1211                         routerRecord.mUserRecord.mHandler,
1212                         routerRecord.mPackageName,
1213                         /* routeListingPreference= */ null));
1214         userRecord.mHandler.sendMessage(
1215                 obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
1216                         userRecord.mHandler));
1217         routerRecord.dispose();
1218         disposeUserIfNeededLocked(userRecord); // since router removed from user
1219     }
1220 
1221     @RequiresPermission(
1222             anyOf = {
1223                 Manifest.permission.MEDIA_ROUTING_CONTROL,
1224                 Manifest.permission.MEDIA_CONTENT_CONTROL
1225             },
1226             conditional = true)
1227     @GuardedBy("mLock")
updateScanningStateLocked( @onNull IMediaRouter2 router, @ScanningState int scanningState)1228     private void updateScanningStateLocked(
1229             @NonNull IMediaRouter2 router, @ScanningState int scanningState) {
1230         final IBinder binder = router.asBinder();
1231         RouterRecord routerRecord = mAllRouterRecords.get(binder);
1232         if (routerRecord == null) {
1233             Slog.w(TAG, "Router record not found. Ignoring updateScanningState call.");
1234             return;
1235         }
1236 
1237         boolean enableScanViaMediaContentControl =
1238                 Flags.enableFullScanWithMediaContentControl()
1239                         && routerRecord.mHasMediaContentControlPermission;
1240         if (scanningState == SCANNING_STATE_SCANNING_FULL
1241                 && !enableScanViaMediaContentControl
1242                 && !routerRecord.mHasMediaRoutingControl) {
1243             throw new SecurityException("Screen off scan requires MEDIA_ROUTING_CONTROL");
1244         }
1245 
1246         Slog.i(
1247                 TAG,
1248                 TextUtils.formatSimple(
1249                         "updateScanningStateLocked | router: %d, packageName: %s, scanningState:"
1250                             + " %d",
1251                         routerRecord.mRouterId,
1252                         routerRecord.mPackageName,
1253                         getScanningStateString(scanningState)));
1254 
1255         routerRecord.updateScanningState(scanningState);
1256     }
1257 
1258     @GuardedBy("mLock")
setDiscoveryRequestWithRouter2Locked(@onNull RouterRecord routerRecord, @NonNull RouteDiscoveryPreference discoveryRequest)1259     private void setDiscoveryRequestWithRouter2Locked(@NonNull RouterRecord routerRecord,
1260             @NonNull RouteDiscoveryPreference discoveryRequest) {
1261         if (routerRecord.mDiscoveryPreference.equals(discoveryRequest)) {
1262             return;
1263         }
1264 
1265         Slog.i(
1266                 TAG,
1267                 TextUtils.formatSimple(
1268                         "setDiscoveryRequestWithRouter2 | router: %s(id: %d), discovery request:"
1269                             + " %s",
1270                         routerRecord.mPackageName,
1271                         routerRecord.mRouterId,
1272                         discoveryRequest.toString()));
1273 
1274         routerRecord.mDiscoveryPreference = discoveryRequest;
1275         routerRecord.mUserRecord.mHandler.sendMessage(
1276                 obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
1277                         routerRecord.mUserRecord.mHandler,
1278                         routerRecord.mPackageName,
1279                         routerRecord.mDiscoveryPreference));
1280         routerRecord.mUserRecord.mHandler.sendMessage(
1281                 obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
1282                         routerRecord.mUserRecord.mHandler));
1283     }
1284 
1285     @GuardedBy("mLock")
setRouteListingPreferenceLocked( RouterRecord routerRecord, @Nullable RouteListingPreference routeListingPreference)1286     private void setRouteListingPreferenceLocked(
1287             RouterRecord routerRecord, @Nullable RouteListingPreference routeListingPreference) {
1288         routerRecord.mRouteListingPreference = routeListingPreference;
1289         String routeListingAsString =
1290                 routeListingPreference != null
1291                         ? routeListingPreference.getItems().stream()
1292                                 .map(RouteListingPreference.Item::getRouteId)
1293                                 .collect(Collectors.joining(","))
1294                         : null;
1295 
1296         Slog.i(
1297                 TAG,
1298                 TextUtils.formatSimple(
1299                         "setRouteListingPreference | router: %s(id: %d), route listing preference:"
1300                             + " [%s]",
1301                         routerRecord.mPackageName, routerRecord.mRouterId, routeListingAsString));
1302 
1303         routerRecord.mUserRecord.mHandler.sendMessage(
1304                 obtainMessage(
1305                         UserHandler::notifyRouteListingPreferenceChangeToManagers,
1306                         routerRecord.mUserRecord.mHandler,
1307                         routerRecord.mPackageName,
1308                         routeListingPreference));
1309     }
1310 
1311     @GuardedBy("mLock")
setRouteVolumeWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull MediaRoute2Info route, int volume)1312     private void setRouteVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
1313             @NonNull MediaRoute2Info route, int volume) {
1314         final IBinder binder = router.asBinder();
1315         RouterRecord routerRecord = mAllRouterRecords.get(binder);
1316 
1317         if (routerRecord != null) {
1318             Slog.i(
1319                     TAG,
1320                     TextUtils.formatSimple(
1321                             "setRouteVolumeWithRouter2 | router: %s(id: %d), volume: %d",
1322                             routerRecord.mPackageName, routerRecord.mRouterId, volume));
1323 
1324             routerRecord.mUserRecord.mHandler.sendMessage(
1325                     obtainMessage(UserHandler::setRouteVolumeOnHandler,
1326                             routerRecord.mUserRecord.mHandler,
1327                             DUMMY_REQUEST_ID, route, volume));
1328         }
1329     }
1330 
1331     @GuardedBy("mLock")
requestCreateSessionWithRouter2Locked( int requestId, long managerRequestId, @NonNull IMediaRouter2 router, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints)1332     private void requestCreateSessionWithRouter2Locked(
1333             int requestId,
1334             long managerRequestId,
1335             @NonNull IMediaRouter2 router,
1336             @NonNull RoutingSessionInfo oldSession,
1337             @NonNull MediaRoute2Info route,
1338             @Nullable Bundle sessionHints) {
1339         final IBinder binder = router.asBinder();
1340         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1341 
1342         if (routerRecord == null) {
1343             return;
1344         }
1345 
1346         Slog.i(
1347                 TAG,
1348                 TextUtils.formatSimple(
1349                         "requestCreateSessionWithRouter2 | router: %s(id: %d), old session id: %s,"
1350                             + " new session's route id: %s, request id: %d",
1351                         routerRecord.mPackageName,
1352                         routerRecord.mRouterId,
1353                         oldSession.getId(),
1354                         route.getId(),
1355                         requestId));
1356 
1357         UserHandler userHandler = routerRecord.mUserRecord.mHandler;
1358         if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) {
1359             ManagerRecord manager = userHandler.findManagerWithId(toRequesterId(managerRequestId));
1360             if (manager == null || manager.mLastSessionCreationRequest == null) {
1361                 Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
1362                         + "Ignoring unknown request.");
1363                 userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId);
1364                 return;
1365             }
1366             if (!TextUtils.equals(manager.mLastSessionCreationRequest.mOldSession.getId(),
1367                     oldSession.getId())) {
1368                 Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
1369                         + "Ignoring unmatched routing session.");
1370                 userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId);
1371                 return;
1372             }
1373             if (!TextUtils.equals(manager.mLastSessionCreationRequest.mRoute.getId(),
1374                     route.getId())) {
1375                 // When media router has no permission
1376                 if (!routerRecord.hasSystemRoutingPermission()
1377                         && manager.mLastSessionCreationRequest.mRoute.isSystemRoute()
1378                         && route.isSystemRoute()) {
1379                     route = manager.mLastSessionCreationRequest.mRoute;
1380                 } else {
1381                     Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
1382                             + "Ignoring unmatched route.");
1383                     userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId);
1384                     return;
1385                 }
1386             }
1387             manager.mLastSessionCreationRequest = null;
1388         } else {
1389             String defaultRouteId = userHandler.mSystemProvider.getDefaultRoute().getId();
1390             if (route.isSystemRoute()
1391                     && !routerRecord.hasSystemRoutingPermission()
1392                     && !TextUtils.equals(route.getId(), defaultRouteId)) {
1393                 Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
1394                         + route);
1395                 userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId);
1396                 return;
1397             }
1398         }
1399 
1400         long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
1401         userHandler.sendMessage(
1402                 obtainMessage(
1403                         UserHandler::requestCreateSessionWithRouter2OnHandler,
1404                         userHandler,
1405                         uniqueRequestId,
1406                         managerRequestId,
1407                         routerRecord,
1408                         oldSession,
1409                         route,
1410                         sessionHints));
1411     }
1412 
1413     @GuardedBy("mLock")
selectRouteWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1414     private void selectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
1415             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1416         final IBinder binder = router.asBinder();
1417         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1418 
1419         if (routerRecord == null) {
1420             return;
1421         }
1422 
1423         Slog.i(
1424                 TAG,
1425                 TextUtils.formatSimple(
1426                         "selectRouteWithRouter2 | router: %s(id: %d), route: %s",
1427                         routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
1428 
1429         routerRecord.mUserRecord.mHandler.sendMessage(
1430                 obtainMessage(UserHandler::selectRouteOnHandler,
1431                         routerRecord.mUserRecord.mHandler,
1432                         DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
1433     }
1434 
1435     @GuardedBy("mLock")
deselectRouteWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1436     private void deselectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
1437             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1438         final IBinder binder = router.asBinder();
1439         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1440 
1441         if (routerRecord == null) {
1442             return;
1443         }
1444 
1445         Slog.i(
1446                 TAG,
1447                 TextUtils.formatSimple(
1448                         "deselectRouteWithRouter2 | router: %s(id: %d), route: %s",
1449                         routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
1450 
1451         routerRecord.mUserRecord.mHandler.sendMessage(
1452                 obtainMessage(UserHandler::deselectRouteOnHandler,
1453                         routerRecord.mUserRecord.mHandler,
1454                         DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
1455     }
1456 
1457     @GuardedBy("mLock")
transferToRouteWithRouter2Locked( @onNull IMediaRouter2 router, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1458     private void transferToRouteWithRouter2Locked(
1459             @NonNull IMediaRouter2 router,
1460             @NonNull UserHandle transferInitiatorUserHandle,
1461             @NonNull String uniqueSessionId,
1462             @NonNull MediaRoute2Info route) {
1463         final IBinder binder = router.asBinder();
1464         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1465 
1466         if (routerRecord == null) {
1467             return;
1468         }
1469 
1470         Slog.i(
1471                 TAG,
1472                 TextUtils.formatSimple(
1473                         "transferToRouteWithRouter2 | router: %s(id: %d), route: %s",
1474                         routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
1475 
1476         UserHandler userHandler = routerRecord.mUserRecord.mHandler;
1477         String defaultRouteId = userHandler.mSystemProvider.getDefaultRoute().getId();
1478         if (route.isSystemRoute()
1479                 && !routerRecord.hasSystemRoutingPermission()
1480                 && !TextUtils.equals(route.getId(), defaultRouteId)) {
1481             userHandler.sendMessage(
1482                     obtainMessage(
1483                             UserHandler::notifySessionCreationFailedToRouter,
1484                             userHandler,
1485                             routerRecord,
1486                             toOriginalRequestId(DUMMY_REQUEST_ID)));
1487         } else {
1488             userHandler.sendMessage(
1489                     obtainMessage(
1490                             UserHandler::transferToRouteOnHandler,
1491                             userHandler,
1492                             DUMMY_REQUEST_ID,
1493                             transferInitiatorUserHandle,
1494                             routerRecord.mPackageName,
1495                             routerRecord,
1496                             uniqueSessionId,
1497                             route,
1498                             RoutingSessionInfo.TRANSFER_REASON_APP));
1499         }
1500     }
1501 
1502     @GuardedBy("mLock")
setSessionVolumeWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, int volume)1503     private void setSessionVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
1504             @NonNull String uniqueSessionId, int volume) {
1505         final IBinder binder = router.asBinder();
1506         RouterRecord routerRecord = mAllRouterRecords.get(binder);
1507 
1508         if (routerRecord == null) {
1509             return;
1510         }
1511 
1512         Slog.i(
1513                 TAG,
1514                 TextUtils.formatSimple(
1515                         "setSessionVolumeWithRouter2 | router: %s(id: %d), session: %s, volume: %d",
1516                         routerRecord.mPackageName,
1517                         routerRecord.mRouterId,
1518                         uniqueSessionId,
1519                         volume));
1520 
1521         routerRecord.mUserRecord.mHandler.sendMessage(
1522                 obtainMessage(UserHandler::setSessionVolumeOnHandler,
1523                         routerRecord.mUserRecord.mHandler,
1524                         DUMMY_REQUEST_ID, uniqueSessionId, volume));
1525     }
1526 
1527     @GuardedBy("mLock")
releaseSessionWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId)1528     private void releaseSessionWithRouter2Locked(@NonNull IMediaRouter2 router,
1529             @NonNull String uniqueSessionId) {
1530         final IBinder binder = router.asBinder();
1531         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1532 
1533         if (routerRecord == null) {
1534             return;
1535         }
1536 
1537         Slog.i(
1538                 TAG,
1539                 TextUtils.formatSimple(
1540                         "releaseSessionWithRouter2 | router: %s(id: %d), session: %s",
1541                         routerRecord.mPackageName, routerRecord.mRouterId, uniqueSessionId));
1542 
1543         routerRecord.mUserRecord.mHandler.sendMessage(
1544                 obtainMessage(UserHandler::releaseSessionOnHandler,
1545                         routerRecord.mUserRecord.mHandler,
1546                         DUMMY_REQUEST_ID, routerRecord, uniqueSessionId));
1547     }
1548 
1549     // End of locked methods that are used by MediaRouter2.
1550 
1551     // Start of locked methods that are used by MediaRouter2Manager.
1552 
1553     @GuardedBy("mLock")
getRemoteSessionsLocked( @onNull IMediaRouter2Manager manager)1554     private List<RoutingSessionInfo> getRemoteSessionsLocked(
1555             @NonNull IMediaRouter2Manager manager) {
1556         final IBinder binder = manager.asBinder();
1557         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1558 
1559         if (managerRecord == null) {
1560             Slog.w(TAG, "getRemoteSessionLocked: Ignoring unknown manager");
1561             return Collections.emptyList();
1562         }
1563 
1564         List<RoutingSessionInfo> sessionInfos = new ArrayList<>();
1565         for (MediaRoute2Provider provider : managerRecord.mUserRecord.mHandler.mRouteProviders) {
1566             if (!provider.mIsSystemRouteProvider) {
1567                 sessionInfos.addAll(provider.getSessionInfos());
1568             }
1569         }
1570         return sessionInfos;
1571     }
1572 
1573     @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
1574     @GuardedBy("mLock")
registerManagerLocked( @onNull IMediaRouter2Manager manager, int callerUid, int callerPid, @NonNull String callerPackageName, @Nullable String targetPackageName, @NonNull UserHandle targetUser)1575     private void registerManagerLocked(
1576             @NonNull IMediaRouter2Manager manager,
1577             int callerUid,
1578             int callerPid,
1579             @NonNull String callerPackageName,
1580             @Nullable String targetPackageName,
1581             @NonNull UserHandle targetUser) {
1582         final IBinder binder = manager.asBinder();
1583         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1584 
1585         if (managerRecord != null) {
1586             Slog.w(TAG, "registerManagerLocked: Same manager already exists. callerPackageName="
1587                     + callerPackageName);
1588             return;
1589         }
1590 
1591         boolean hasMediaRoutingControl =
1592                 checkMediaRoutingControlPermission(callerUid, callerPid, callerPackageName);
1593 
1594         boolean hasMediaContentControl = checkMediaContentControlPermission(callerUid, callerPid);
1595 
1596         Slog.i(
1597                 TAG,
1598                 TextUtils.formatSimple(
1599                         "registerManager | callerUid: %d, callerPid: %d, callerPackage: %s,"
1600                             + " targetPackageName: %s, targetUserId: %d, hasMediaRoutingControl:"
1601                             + " %b",
1602                         callerUid,
1603                         callerPid,
1604                         callerPackageName,
1605                         targetPackageName,
1606                         targetUser,
1607                         hasMediaRoutingControl));
1608 
1609         UserRecord userRecord = getOrCreateUserRecordLocked(targetUser.getIdentifier());
1610 
1611         managerRecord =
1612                 new ManagerRecord(
1613                         userRecord,
1614                         manager,
1615                         callerUid,
1616                         callerPid,
1617                         callerPackageName,
1618                         targetPackageName,
1619                         hasMediaRoutingControl,
1620                         hasMediaContentControl);
1621         try {
1622             binder.linkToDeath(managerRecord, 0);
1623         } catch (RemoteException ex) {
1624             throw new RuntimeException("Media router manager died prematurely.", ex);
1625         }
1626 
1627         userRecord.mManagerRecords.add(managerRecord);
1628         mAllManagerRecords.put(binder, managerRecord);
1629 
1630         // Note: Features should be sent first before the routes. If not, the
1631         // RouteCallback#onRoutesAdded() for system MR2 will never be called with initial routes
1632         // due to the lack of features.
1633         for (RouterRecord routerRecord : userRecord.mRouterRecords) {
1634             // Send route listing preferences before discovery preferences and routes to avoid an
1635             // inconsistent state where there are routes to show, but the manager thinks
1636             // the app has not expressed a preference for listing.
1637             userRecord.mHandler.sendMessage(
1638                     obtainMessage(
1639                             UserHandler::notifyRouteListingPreferenceChangeToManagers,
1640                             routerRecord.mUserRecord.mHandler,
1641                             routerRecord.mPackageName,
1642                             routerRecord.mRouteListingPreference));
1643             // TODO: UserRecord <-> routerRecord, why do they reference each other?
1644             // How about removing mUserRecord from routerRecord?
1645             routerRecord.mUserRecord.mHandler.sendMessage(
1646                     obtainMessage(
1647                             UserHandler::notifyDiscoveryPreferenceChangedToManager,
1648                             routerRecord.mUserRecord.mHandler,
1649                             routerRecord,
1650                             manager));
1651         }
1652 
1653         userRecord.mHandler.sendMessage(
1654                 obtainMessage(
1655                         UserHandler::notifyInitialRoutesToManager, userRecord.mHandler, manager));
1656     }
1657 
1658     @GuardedBy("mLock")
unregisterManagerLocked(@onNull IMediaRouter2Manager manager, boolean died)1659     private void unregisterManagerLocked(@NonNull IMediaRouter2Manager manager, boolean died) {
1660         ManagerRecord managerRecord = mAllManagerRecords.remove(manager.asBinder());
1661         if (managerRecord == null) {
1662             Slog.w(
1663                     TAG,
1664                     TextUtils.formatSimple(
1665                             "Ignoring unregistering unknown manager: %s, died: %b", manager, died));
1666             return;
1667         }
1668         UserRecord userRecord = managerRecord.mUserRecord;
1669 
1670         Slog.i(
1671                 TAG,
1672                 TextUtils.formatSimple(
1673                         "unregisterManager | package: %s, user: %d, manager: %d, died: %b",
1674                         managerRecord.mOwnerPackageName,
1675                         userRecord.mUserId,
1676                         managerRecord.mManagerId,
1677                         died));
1678 
1679         userRecord.mManagerRecords.remove(managerRecord);
1680         managerRecord.dispose();
1681         disposeUserIfNeededLocked(userRecord); // since manager removed from user
1682     }
1683 
1684     @GuardedBy("mLock")
updateScanningStateLocked( @onNull IMediaRouter2Manager manager, @ScanningState int scanningState)1685     private void updateScanningStateLocked(
1686             @NonNull IMediaRouter2Manager manager, @ScanningState int scanningState) {
1687         final IBinder binder = manager.asBinder();
1688         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1689         if (managerRecord == null) {
1690             Slog.w(TAG, "Manager record not found. Ignoring updateScanningState call.");
1691             return;
1692         }
1693 
1694         boolean enableScanViaMediaContentControl =
1695                 Flags.enableFullScanWithMediaContentControl()
1696                         && managerRecord.mHasMediaContentControl;
1697         if (!managerRecord.mHasMediaRoutingControl
1698                 && !enableScanViaMediaContentControl
1699                 && scanningState == SCANNING_STATE_SCANNING_FULL) {
1700             throw new SecurityException("Screen off scan requires MEDIA_ROUTING_CONTROL");
1701         }
1702 
1703         Slog.i(
1704                 TAG,
1705                 TextUtils.formatSimple(
1706                         "updateScanningState | manager: %d, ownerPackageName: %s,"
1707                             + " targetPackageName: %s, scanningState: %d",
1708                         managerRecord.mManagerId,
1709                         managerRecord.mOwnerPackageName,
1710                         managerRecord.mTargetPackageName,
1711                         getScanningStateString(scanningState)));
1712 
1713         managerRecord.updateScanningState(scanningState);
1714     }
1715 
1716     @GuardedBy("mLock")
setRouteVolumeWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull MediaRoute2Info route, int volume)1717     private void setRouteVolumeWithManagerLocked(int requestId,
1718             @NonNull IMediaRouter2Manager manager,
1719             @NonNull MediaRoute2Info route, int volume) {
1720         final IBinder binder = manager.asBinder();
1721         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1722 
1723         if (managerRecord == null) {
1724             return;
1725         }
1726 
1727         Slog.i(TAG, TextUtils.formatSimple(
1728                 "setRouteVolumeWithManager | manager: %d, route: %s, volume: %d",
1729                 managerRecord.mManagerId, route.getId(), volume));
1730 
1731         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1732         managerRecord.mUserRecord.mHandler.sendMessage(
1733                 obtainMessage(UserHandler::setRouteVolumeOnHandler,
1734                         managerRecord.mUserRecord.mHandler,
1735                         uniqueRequestId, route, volume));
1736     }
1737 
1738     @GuardedBy("mLock")
requestCreateSessionWithManagerLocked( int requestId, @NonNull IMediaRouter2Manager manager, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName)1739     private void requestCreateSessionWithManagerLocked(
1740             int requestId,
1741             @NonNull IMediaRouter2Manager manager,
1742             @NonNull RoutingSessionInfo oldSession,
1743             @NonNull MediaRoute2Info route,
1744             @NonNull UserHandle transferInitiatorUserHandle,
1745             @NonNull String transferInitiatorPackageName) {
1746         ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
1747         if (managerRecord == null) {
1748             return;
1749         }
1750 
1751         Slog.i(TAG, TextUtils.formatSimple(
1752                 "requestCreateSessionWithManager | manager: %d, route: %s",
1753                 managerRecord.mManagerId, route.getId()));
1754 
1755         String packageName = oldSession.getClientPackageName();
1756 
1757         RouterRecord routerRecord = managerRecord.mUserRecord.findRouterRecordLocked(packageName);
1758         if (routerRecord == null) {
1759             Slog.w(TAG, "requestCreateSessionWithManagerLocked: Ignoring session creation for "
1760                     + "unknown router.");
1761             try {
1762                 managerRecord.mManager.notifyRequestFailed(requestId, REASON_UNKNOWN_ERROR);
1763             } catch (RemoteException ex) {
1764                 Slog.w(TAG, "requestCreateSessionWithManagerLocked: Failed to notify failure. "
1765                         + "Manager probably died.");
1766             }
1767             return;
1768         }
1769 
1770         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1771         SessionCreationRequest lastRequest = managerRecord.mLastSessionCreationRequest;
1772         if (lastRequest != null) {
1773             Slog.i(
1774                     TAG,
1775                     TextUtils.formatSimple(
1776                             "requestCreateSessionWithManagerLocked: Notifying failure for pending"
1777                                 + " session creation request - oldSession: %s, route: %s",
1778                             lastRequest.mOldSession, lastRequest.mRoute));
1779             managerRecord.mUserRecord.mHandler.notifyRequestFailedToManager(
1780                     managerRecord.mManager,
1781                     toOriginalRequestId(lastRequest.mManagerRequestId),
1782                     REASON_UNKNOWN_ERROR);
1783         }
1784         managerRecord.mLastSessionCreationRequest = new SessionCreationRequest(routerRecord,
1785                 MediaRoute2ProviderService.REQUEST_ID_NONE, uniqueRequestId,
1786                 oldSession, route);
1787 
1788         // Before requesting to the provider, get session hints from the media router.
1789         // As a return, media router will request to create a session.
1790         routerRecord.mUserRecord.mHandler.sendMessage(
1791                 obtainMessage(
1792                         UserHandler::requestRouterCreateSessionOnHandler,
1793                         routerRecord.mUserRecord.mHandler,
1794                         uniqueRequestId,
1795                         routerRecord,
1796                         managerRecord,
1797                         oldSession,
1798                         route,
1799                         transferInitiatorUserHandle,
1800                         transferInitiatorPackageName));
1801     }
1802 
1803     @GuardedBy("mLock")
selectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1804     private void selectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager,
1805             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1806         final IBinder binder = manager.asBinder();
1807         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1808 
1809         if (managerRecord == null) {
1810             return;
1811         }
1812 
1813         Slog.i(TAG, TextUtils.formatSimple(
1814                 "selectRouteWithManager | manager: %d, session: %s, route: %s",
1815                 managerRecord.mManagerId, uniqueSessionId, route.getId()));
1816 
1817         // Can be null if the session is system's or RCN.
1818         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
1819                 .findRouterWithSessionLocked(uniqueSessionId);
1820 
1821         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1822         managerRecord.mUserRecord.mHandler.sendMessage(
1823                 obtainMessage(UserHandler::selectRouteOnHandler,
1824                         managerRecord.mUserRecord.mHandler,
1825                         uniqueRequestId, routerRecord, uniqueSessionId, route));
1826     }
1827 
1828     @GuardedBy("mLock")
deselectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1829     private void deselectRouteWithManagerLocked(int requestId,
1830             @NonNull IMediaRouter2Manager manager,
1831             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1832         final IBinder binder = manager.asBinder();
1833         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1834 
1835         if (managerRecord == null) {
1836             return;
1837         }
1838 
1839         Slog.i(TAG, TextUtils.formatSimple(
1840                 "deselectRouteWithManager | manager: %d, session: %s, route: %s",
1841                 managerRecord.mManagerId, uniqueSessionId, route.getId()));
1842 
1843         // Can be null if the session is system's or RCN.
1844         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
1845                 .findRouterWithSessionLocked(uniqueSessionId);
1846 
1847         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1848         managerRecord.mUserRecord.mHandler.sendMessage(
1849                 obtainMessage(UserHandler::deselectRouteOnHandler,
1850                         managerRecord.mUserRecord.mHandler,
1851                         uniqueRequestId, routerRecord, uniqueSessionId, route));
1852     }
1853 
1854     @GuardedBy("mLock")
transferToRouteWithManagerLocked( int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, @RoutingSessionInfo.TransferReason int transferReason, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName)1855     private void transferToRouteWithManagerLocked(
1856             int requestId,
1857             @NonNull IMediaRouter2Manager manager,
1858             @NonNull String uniqueSessionId,
1859             @NonNull MediaRoute2Info route,
1860             @RoutingSessionInfo.TransferReason int transferReason,
1861             @NonNull UserHandle transferInitiatorUserHandle,
1862             @NonNull String transferInitiatorPackageName) {
1863         final IBinder binder = manager.asBinder();
1864         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1865 
1866         if (managerRecord == null) {
1867             return;
1868         }
1869 
1870         Slog.i(TAG, TextUtils.formatSimple(
1871                 "transferToRouteWithManager | manager: %d, session: %s, route: %s",
1872                 managerRecord.mManagerId, uniqueSessionId, route.getId()));
1873 
1874         // Can be null if the session is system's or RCN.
1875         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
1876                 .findRouterWithSessionLocked(uniqueSessionId);
1877 
1878         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1879         managerRecord.mUserRecord.mHandler.sendMessage(
1880                 obtainMessage(
1881                         UserHandler::transferToRouteOnHandler,
1882                         managerRecord.mUserRecord.mHandler,
1883                         uniqueRequestId,
1884                         transferInitiatorUserHandle,
1885                         transferInitiatorPackageName,
1886                         routerRecord,
1887                         uniqueSessionId,
1888                         route,
1889                         transferReason));
1890     }
1891 
1892     @GuardedBy("mLock")
setSessionVolumeWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, int volume)1893     private void setSessionVolumeWithManagerLocked(int requestId,
1894             @NonNull IMediaRouter2Manager manager,
1895             @NonNull String uniqueSessionId, int volume) {
1896         final IBinder binder = manager.asBinder();
1897         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1898 
1899         if (managerRecord == null) {
1900             return;
1901         }
1902 
1903         Slog.i(TAG, TextUtils.formatSimple(
1904                 "setSessionVolumeWithManager | manager: %d, session: %s, volume: %d",
1905                 managerRecord.mManagerId, uniqueSessionId, volume));
1906 
1907         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1908         managerRecord.mUserRecord.mHandler.sendMessage(
1909                 obtainMessage(UserHandler::setSessionVolumeOnHandler,
1910                         managerRecord.mUserRecord.mHandler,
1911                         uniqueRequestId, uniqueSessionId, volume));
1912     }
1913 
1914     @GuardedBy("mLock")
releaseSessionWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId)1915     private void releaseSessionWithManagerLocked(int requestId,
1916             @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId) {
1917         final IBinder binder = manager.asBinder();
1918         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1919 
1920         if (managerRecord == null) {
1921             return;
1922         }
1923 
1924         Slog.i(TAG, TextUtils.formatSimple(
1925                 "releaseSessionWithManager | manager: %d, session: %s",
1926                 managerRecord.mManagerId, uniqueSessionId));
1927 
1928         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
1929                 .findRouterWithSessionLocked(uniqueSessionId);
1930 
1931         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1932         managerRecord.mUserRecord.mHandler.sendMessage(
1933                 obtainMessage(UserHandler::releaseSessionOnHandler,
1934                         managerRecord.mUserRecord.mHandler,
1935                         uniqueRequestId, routerRecord, uniqueSessionId));
1936     }
1937 
1938     // End of locked methods that are used by MediaRouter2Manager.
1939 
1940     // Start of locked methods that are used by both MediaRouter2 and MediaRouter2Manager.
1941 
1942     @GuardedBy("mLock")
getOrCreateUserRecordLocked(int userId)1943     private UserRecord getOrCreateUserRecordLocked(int userId) {
1944         UserRecord userRecord = mUserRecords.get(userId);
1945         if (userRecord == null) {
1946             userRecord = new UserRecord(userId, mLooper);
1947             mUserRecords.put(userId, userRecord);
1948             userRecord.init();
1949             if (isUserActiveLocked(userId)) {
1950                 userRecord.mHandler.sendMessage(
1951                         obtainMessage(UserHandler::start, userRecord.mHandler));
1952             }
1953         }
1954         return userRecord;
1955     }
1956 
1957     @GuardedBy("mLock")
disposeUserIfNeededLocked(@onNull UserRecord userRecord)1958     private void disposeUserIfNeededLocked(@NonNull UserRecord userRecord) {
1959         // If there are no records left and the user is no longer current then go ahead
1960         // and purge the user record and all of its associated state.  If the user is current
1961         // then leave it alone since we might be connected to a route or want to query
1962         // the same route information again soon.
1963         if (!isUserActiveLocked(userRecord.mUserId)
1964                 && userRecord.mRouterRecords.isEmpty()
1965                 && userRecord.mManagerRecords.isEmpty()) {
1966             if (DEBUG) {
1967                 Slog.d(TAG, userRecord + ": Disposed");
1968             }
1969             userRecord.mHandler.sendMessage(
1970                     obtainMessage(UserHandler::stop, userRecord.mHandler));
1971             mUserRecords.remove(userRecord.mUserId);
1972             // Note: User already stopped (by switchUser) so no need to send stop message here.
1973         }
1974     }
1975 
1976     // End of locked methods that are used by both MediaRouter2 and MediaRouter2Manager.
1977 
toUniqueRequestId(int requesterId, int originalRequestId)1978     static long toUniqueRequestId(int requesterId, int originalRequestId) {
1979         return ((long) requesterId << 32) | originalRequestId;
1980     }
1981 
toRequesterId(long uniqueRequestId)1982     static int toRequesterId(long uniqueRequestId) {
1983         return (int) (uniqueRequestId >> 32);
1984     }
1985 
toOriginalRequestId(long uniqueRequestId)1986     static int toOriginalRequestId(long uniqueRequestId) {
1987         return (int) uniqueRequestId;
1988     }
1989 
getScanningStateString(@canningState int scanningState)1990     private static String getScanningStateString(@ScanningState int scanningState) {
1991         return switch (scanningState) {
1992             case SCANNING_STATE_NOT_SCANNING -> "NOT_SCANNING";
1993             case SCANNING_STATE_WHILE_INTERACTIVE -> "SCREEN_ON_ONLY";
1994             case SCANNING_STATE_SCANNING_FULL -> "FULL";
1995             default -> "Invalid scanning state: " + scanningState;
1996         };
1997     }
1998 
validateScanningStateValue(@canningState int scanningState)1999     private static void validateScanningStateValue(@ScanningState int scanningState) {
2000         if (scanningState != SCANNING_STATE_NOT_SCANNING
2001                 && scanningState != SCANNING_STATE_WHILE_INTERACTIVE
2002                 && scanningState != SCANNING_STATE_SCANNING_FULL) {
2003             throw new IllegalArgumentException(
2004                     TextUtils.formatSimple("Scanning state %d is not valid.", scanningState));
2005         }
2006     }
2007 
2008     final class UserRecord {
2009         public final int mUserId;
2010         //TODO: make records private for thread-safety
2011         final ArrayList<RouterRecord> mRouterRecords = new ArrayList<>();
2012         final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>();
2013         RouteDiscoveryPreference mCompositeDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
2014         Set<String> mActivelyScanningPackages = Set.of();
2015         final UserHandler mHandler;
2016 
UserRecord(int userId, @NonNull Looper looper)2017         UserRecord(int userId, @NonNull Looper looper) {
2018             mUserId = userId;
2019             mHandler =
2020                     new UserHandler(
2021                             /* service= */ MediaRouter2ServiceImpl.this,
2022                             /* userRecord= */ this,
2023                             looper);
2024         }
2025 
init()2026         void init() {
2027             mHandler.init();
2028         }
2029 
2030         // TODO: This assumes that only one router exists in a package.
2031         //       Do this in Android S or later.
2032         @GuardedBy("mLock")
findRouterRecordLocked(String packageName)2033         RouterRecord findRouterRecordLocked(String packageName) {
2034             for (RouterRecord routerRecord : mRouterRecords) {
2035                 if (TextUtils.equals(routerRecord.mPackageName, packageName)) {
2036                     return routerRecord;
2037                 }
2038             }
2039             return null;
2040         }
2041 
dump(@onNull PrintWriter pw, @NonNull String prefix)2042         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
2043             pw.println(prefix + "UserRecord");
2044 
2045             String indent = prefix + "  ";
2046 
2047             pw.println(indent + "mUserId=" + mUserId);
2048 
2049             pw.println(indent + "Router Records:");
2050             if (!mRouterRecords.isEmpty()) {
2051                 for (RouterRecord routerRecord : mRouterRecords) {
2052                     routerRecord.dump(pw, indent + "  ");
2053                 }
2054             } else {
2055                 pw.println(indent + "<no router records>");
2056             }
2057 
2058             pw.println(indent + "Manager Records:");
2059             if (!mManagerRecords.isEmpty()) {
2060                 for (ManagerRecord managerRecord : mManagerRecords) {
2061                     managerRecord.dump(pw, indent + "  ");
2062                 }
2063             } else {
2064                 pw.println(indent + "<no manager records>");
2065             }
2066 
2067             pw.println(indent + "Composite discovery preference:");
2068             mCompositeDiscoveryPreference.dump(pw, indent + "  ");
2069             pw.println(
2070                     indent
2071                             + "Packages actively scanning: "
2072                             + String.join(", ", mActivelyScanningPackages));
2073 
2074             if (!mHandler.runWithScissors(() -> mHandler.dump(pw, indent), 1000)) {
2075                 pw.println(indent + "<could not dump handler state>");
2076             }
2077         }
2078     }
2079 
2080     final class RouterRecord implements IBinder.DeathRecipient {
2081         public final UserRecord mUserRecord;
2082         public final String mPackageName;
2083         public final List<Integer> mSelectRouteSequenceNumbers;
2084         public final IMediaRouter2 mRouter;
2085         public final int mUid;
2086         public final int mPid;
2087         public final boolean mHasConfigureWifiDisplayPermission;
2088         public final boolean mHasModifyAudioRoutingPermission;
2089         public final boolean mHasMediaContentControlPermission;
2090         public final boolean mHasMediaRoutingControl;
2091         public final AtomicBoolean mHasBluetoothRoutingPermission;
2092         public final int mRouterId;
2093         public @ScanningState int mScanningState = SCANNING_STATE_NOT_SCANNING;
2094 
2095         public RouteDiscoveryPreference mDiscoveryPreference;
2096         @Nullable public RouteListingPreference mRouteListingPreference;
2097 
RouterRecord( UserRecord userRecord, IMediaRouter2 router, int uid, int pid, String packageName, boolean hasConfigureWifiDisplayPermission, boolean hasModifyAudioRoutingPermission, boolean hasMediaContentControlPermission, boolean hasMediaRoutingControl)2098         RouterRecord(
2099                 UserRecord userRecord,
2100                 IMediaRouter2 router,
2101                 int uid,
2102                 int pid,
2103                 String packageName,
2104                 boolean hasConfigureWifiDisplayPermission,
2105                 boolean hasModifyAudioRoutingPermission,
2106                 boolean hasMediaContentControlPermission,
2107                 boolean hasMediaRoutingControl) {
2108             mUserRecord = userRecord;
2109             mPackageName = packageName;
2110             mSelectRouteSequenceNumbers = new ArrayList<>();
2111             mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
2112             mRouter = router;
2113             mUid = uid;
2114             mPid = pid;
2115             mHasConfigureWifiDisplayPermission = hasConfigureWifiDisplayPermission;
2116             mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission;
2117             mHasMediaContentControlPermission = hasMediaContentControlPermission;
2118             mHasMediaRoutingControl = hasMediaRoutingControl;
2119             mHasBluetoothRoutingPermission =
2120                     new AtomicBoolean(checkCallerHasBluetoothPermissions(mPid, mUid));
2121             mRouterId = mNextRouterOrManagerId.getAndIncrement();
2122         }
2123 
2124         /**
2125          * Returns whether the corresponding router has permission to query and control system
2126          * routes.
2127          */
hasSystemRoutingPermission()2128         public boolean hasSystemRoutingPermission() {
2129             return mHasModifyAudioRoutingPermission || mHasBluetoothRoutingPermission.get();
2130         }
2131 
isActivelyScanning()2132         public boolean isActivelyScanning() {
2133             return mScanningState == SCANNING_STATE_WHILE_INTERACTIVE
2134                     || mScanningState == SCANNING_STATE_SCANNING_FULL
2135                     || mDiscoveryPreference.shouldPerformActiveScan();
2136         }
2137 
2138         @GuardedBy("mLock")
maybeUpdateSystemRoutingPermissionLocked()2139         public void maybeUpdateSystemRoutingPermissionLocked() {
2140             boolean oldSystemRoutingPermissionValue = hasSystemRoutingPermission();
2141             mHasBluetoothRoutingPermission.set(checkCallerHasBluetoothPermissions(mPid, mUid));
2142             boolean newSystemRoutingPermissionValue = hasSystemRoutingPermission();
2143             if (oldSystemRoutingPermissionValue != newSystemRoutingPermissionValue) {
2144                 Map<String, MediaRoute2Info> routesToReport =
2145                         newSystemRoutingPermissionValue
2146                                 ? mUserRecord.mHandler.mLastNotifiedRoutesToPrivilegedRouters
2147                                 : mUserRecord.mHandler.mLastNotifiedRoutesToNonPrivilegedRouters;
2148                 notifyRoutesUpdated(routesToReport.values().stream().toList());
2149 
2150                 List<RoutingSessionInfo> sessionInfos =
2151                         mUserRecord.mHandler.mSystemProvider.getSessionInfos();
2152                 RoutingSessionInfo systemSessionToReport =
2153                         newSystemRoutingPermissionValue && !sessionInfos.isEmpty()
2154                                 ? sessionInfos.get(0)
2155                                 : mUserRecord.mHandler.mSystemProvider.getDefaultSessionInfo();
2156                 notifySessionInfoChanged(systemSessionToReport);
2157             }
2158         }
2159 
dispose()2160         public void dispose() {
2161             mRouter.asBinder().unlinkToDeath(this, 0);
2162         }
2163 
2164         @Override
binderDied()2165         public void binderDied() {
2166             routerDied(this);
2167         }
2168 
updateScanningState(@canningState int scanningState)2169         public void updateScanningState(@ScanningState int scanningState) {
2170             if (mScanningState == scanningState) {
2171                 return;
2172             }
2173 
2174             mScanningState = scanningState;
2175 
2176             mUserRecord.mHandler.sendMessage(
2177                     obtainMessage(
2178                             UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
2179         }
2180 
dump(@onNull PrintWriter pw, @NonNull String prefix)2181         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
2182             pw.println(prefix + "RouterRecord");
2183 
2184             String indent = prefix + "  ";
2185 
2186             pw.println(indent + "mPackageName=" + mPackageName);
2187             pw.println(indent + "mSelectRouteSequenceNumbers=" + mSelectRouteSequenceNumbers);
2188             pw.println(indent + "mUid=" + mUid);
2189             pw.println(indent + "mPid=" + mPid);
2190             pw.println(indent + "mHasConfigureWifiDisplayPermission="
2191                     + mHasConfigureWifiDisplayPermission);
2192             pw.println(
2193                     indent
2194                             + "mHasModifyAudioRoutingPermission="
2195                             + mHasModifyAudioRoutingPermission);
2196             pw.println(
2197                     indent
2198                             + "mHasBluetoothRoutingPermission="
2199                             + mHasBluetoothRoutingPermission.get());
2200             pw.println(indent + "hasSystemRoutingPermission=" + hasSystemRoutingPermission());
2201             pw.println(indent + "mRouterId=" + mRouterId);
2202 
2203             mDiscoveryPreference.dump(pw, indent);
2204         }
2205 
2206         /**
2207          * Notifies the corresponding router that it was successfully registered.
2208          *
2209          * <p>The message sent to the router includes a snapshot of the initial state, including
2210          * known routes and the system {@link RoutingSessionInfo}.
2211          *
2212          * @param currentRoutes All currently known routes, which are filtered according to package
2213          *     visibility before being sent to the router.
2214          * @param currentSystemSessionInfo The current system {@link RoutingSessionInfo}.
2215          */
notifyRegistered( List<MediaRoute2Info> currentRoutes, RoutingSessionInfo currentSystemSessionInfo)2216         public void notifyRegistered(
2217                 List<MediaRoute2Info> currentRoutes, RoutingSessionInfo currentSystemSessionInfo) {
2218             try {
2219                 mRouter.notifyRouterRegistered(
2220                         getVisibleRoutes(currentRoutes), currentSystemSessionInfo);
2221             } catch (RemoteException ex) {
2222                 Slog.w(TAG, "Failed to notify router registered. Router probably died.", ex);
2223             }
2224         }
2225 
2226         /**
2227          * Sends the corresponding router an {@link
2228          * android.media.MediaRouter2.RouteCallback#onRoutesUpdated update} for the given {@code
2229          * routes}.
2230          *
2231          * <p>Only the routes that are visible to the router are sent as part of the update.
2232          */
notifyRoutesUpdated(List<MediaRoute2Info> routes)2233         public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
2234             try {
2235                 mRouter.notifyRoutesUpdated(getVisibleRoutes(routes));
2236             } catch (RemoteException ex) {
2237                 Slog.w(TAG, "Failed to notify routes updated. Router probably died.", ex);
2238             }
2239         }
2240 
notifySessionCreated(int requestId, @NonNull RoutingSessionInfo sessionInfo)2241         public void notifySessionCreated(int requestId, @NonNull RoutingSessionInfo sessionInfo) {
2242             try {
2243                 mRouter.notifySessionCreated(
2244                         requestId, maybeClearTransferInitiatorIdentity(sessionInfo));
2245             } catch (RemoteException ex) {
2246                 Slog.w(
2247                         TAG,
2248                         "Failed to notify router of the session creation."
2249                                 + " Router probably died.",
2250                         ex);
2251             }
2252         }
2253 
2254         /**
2255          * Sends the corresponding router an update for the given session.
2256          *
2257          * <p>Note: These updates are not directly visible to the app.
2258          */
notifySessionInfoChanged(RoutingSessionInfo sessionInfo)2259         public void notifySessionInfoChanged(RoutingSessionInfo sessionInfo) {
2260             try {
2261                 mRouter.notifySessionInfoChanged(maybeClearTransferInitiatorIdentity(sessionInfo));
2262             } catch (RemoteException ex) {
2263                 Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
2264             }
2265         }
2266 
maybeClearTransferInitiatorIdentity( @onNull RoutingSessionInfo sessionInfo)2267         private RoutingSessionInfo maybeClearTransferInitiatorIdentity(
2268                 @NonNull RoutingSessionInfo sessionInfo) {
2269             UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
2270             String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
2271 
2272             if (!Objects.equals(UserHandle.of(mUserRecord.mUserId), transferInitiatorUserHandle)
2273                     || !Objects.equals(mPackageName, transferInitiatorPackageName)) {
2274                 return new RoutingSessionInfo.Builder(sessionInfo)
2275                         .setTransferInitiator(null, null)
2276                         .build();
2277             }
2278 
2279             return sessionInfo;
2280         }
2281 
2282         /**
2283          * Returns a filtered copy of {@code routes} that contains only the routes that are {@link
2284          * MediaRoute2Info#isVisibleTo visible} to the router corresponding to this record.
2285          */
getVisibleRoutes(@onNull List<MediaRoute2Info> routes)2286         private List<MediaRoute2Info> getVisibleRoutes(@NonNull List<MediaRoute2Info> routes) {
2287             List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
2288             for (MediaRoute2Info route : routes) {
2289                 if (route.isVisibleTo(mPackageName)) {
2290                     filteredRoutes.add(route);
2291                 }
2292             }
2293             return filteredRoutes;
2294         }
2295     }
2296 
2297     final class ManagerRecord implements IBinder.DeathRecipient {
2298         @NonNull public final UserRecord mUserRecord;
2299         @NonNull public final IMediaRouter2Manager mManager;
2300         public final int mOwnerUid;
2301         public final int mOwnerPid;
2302         @NonNull public final String mOwnerPackageName;
2303         public final int mManagerId;
2304         // TODO (b/281072508): Document behaviour around nullability for mTargetPackageName.
2305         @Nullable public final String mTargetPackageName;
2306 
2307         public final boolean mHasMediaRoutingControl;
2308         public final boolean mHasMediaContentControl;
2309         @Nullable public SessionCreationRequest mLastSessionCreationRequest;
2310 
2311         public @ScanningState int mScanningState = SCANNING_STATE_NOT_SCANNING;
2312 
ManagerRecord( @onNull UserRecord userRecord, @NonNull IMediaRouter2Manager manager, int ownerUid, int ownerPid, @NonNull String ownerPackageName, @Nullable String targetPackageName, boolean hasMediaRoutingControl, boolean hasMediaContentControl)2313         ManagerRecord(
2314                 @NonNull UserRecord userRecord,
2315                 @NonNull IMediaRouter2Manager manager,
2316                 int ownerUid,
2317                 int ownerPid,
2318                 @NonNull String ownerPackageName,
2319                 @Nullable String targetPackageName,
2320                 boolean hasMediaRoutingControl,
2321                 boolean hasMediaContentControl) {
2322             mUserRecord = userRecord;
2323             mManager = manager;
2324             mOwnerUid = ownerUid;
2325             mOwnerPid = ownerPid;
2326             mOwnerPackageName = ownerPackageName;
2327             mTargetPackageName = targetPackageName;
2328             mManagerId = mNextRouterOrManagerId.getAndIncrement();
2329             mHasMediaRoutingControl = hasMediaRoutingControl;
2330             mHasMediaContentControl = hasMediaContentControl;
2331         }
2332 
dispose()2333         public void dispose() {
2334             mManager.asBinder().unlinkToDeath(this, 0);
2335         }
2336 
2337         @Override
binderDied()2338         public void binderDied() {
2339             managerDied(this);
2340         }
2341 
dump(@onNull PrintWriter pw, @NonNull String prefix)2342         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
2343             pw.println(prefix + "ManagerRecord");
2344 
2345             String indent = prefix + "  ";
2346 
2347             pw.println(indent + "mOwnerPackageName=" + mOwnerPackageName);
2348             pw.println(indent + "mTargetPackageName=" + mTargetPackageName);
2349             pw.println(indent + "mManagerId=" + mManagerId);
2350             pw.println(indent + "mOwnerUid=" + mOwnerUid);
2351             pw.println(indent + "mOwnerPid=" + mOwnerPid);
2352             pw.println(indent + "mScanningState=" + getScanningStateString(mScanningState));
2353 
2354             if (mLastSessionCreationRequest != null) {
2355                 mLastSessionCreationRequest.dump(pw, indent);
2356             }
2357         }
2358 
updateScanningState(@canningState int scanningState)2359         private void updateScanningState(@ScanningState int scanningState) {
2360             if (mScanningState == scanningState) {
2361                 return;
2362             }
2363 
2364             mScanningState = scanningState;
2365 
2366             mUserRecord.mHandler.sendMessage(
2367                     obtainMessage(
2368                             UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
2369         }
2370 
2371         @Override
toString()2372         public String toString() {
2373             return "Manager " + mOwnerPackageName + " (pid " + mOwnerPid + ")";
2374         }
2375     }
2376 
2377     static final class UserHandler extends Handler implements
2378             MediaRoute2ProviderWatcher.Callback,
2379             MediaRoute2Provider.Callback {
2380 
2381         private final WeakReference<MediaRouter2ServiceImpl> mServiceRef;
2382         private final UserRecord mUserRecord;
2383         private final MediaRoute2ProviderWatcher mWatcher;
2384 
2385         private final SystemMediaRoute2Provider mSystemProvider;
2386         private final ArrayList<MediaRoute2Provider> mRouteProviders =
2387                 new ArrayList<>();
2388 
2389         private final List<MediaRoute2ProviderInfo> mLastProviderInfos = new ArrayList<>();
2390         private final CopyOnWriteArrayList<SessionCreationRequest> mSessionCreationRequests =
2391                 new CopyOnWriteArrayList<>();
2392         private final Map<String, RouterRecord> mSessionToRouterMap = new ArrayMap<>();
2393 
2394         /**
2395          * Latest list of routes sent to privileged {@link android.media.MediaRouter2 routers} and
2396          * {@link android.media.MediaRouter2Manager managers}.
2397          *
2398          * <p>Privileged routers are instances of {@link android.media.MediaRouter2 MediaRouter2}
2399          * that have {@code MODIFY_AUDIO_ROUTING} permission.
2400          *
2401          * <p>This list contains all routes exposed by route providers. This includes routes from
2402          * both system route providers and user route providers.
2403          *
2404          * <p>See {@link #getRouterRecords(boolean hasModifyAudioRoutingPermission)}.
2405          */
2406         private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToPrivilegedRouters =
2407                 new ArrayMap<>();
2408 
2409         /**
2410          * Latest list of routes sent to non-privileged {@link android.media.MediaRouter2 routers}.
2411          *
2412          * <p>Non-privileged routers are instances of {@link android.media.MediaRouter2
2413          * MediaRouter2} that do <i><b>not</b></i> have {@code MODIFY_AUDIO_ROUTING} permission.
2414          *
2415          * <p>This list contains all routes exposed by user route providers. It might also include
2416          * the current default route from {@link #mSystemProvider} to expose local route updates
2417          * (e.g. volume changes) to non-privileged routers.
2418          *
2419          * <p>See {@link SystemMediaRoute2Provider#mDefaultRoute}.
2420          */
2421         private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToNonPrivilegedRouters =
2422                 new ArrayMap<>();
2423 
2424         private boolean mRunning;
2425 
2426         // TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler.
UserHandler( @onNull MediaRouter2ServiceImpl service, @NonNull UserRecord userRecord, @NonNull Looper looper)2427         UserHandler(
2428                 @NonNull MediaRouter2ServiceImpl service,
2429                 @NonNull UserRecord userRecord,
2430                 @NonNull Looper looper) {
2431             super(looper, /* callback= */ null, /* async= */ true);
2432             mServiceRef = new WeakReference<>(service);
2433             mUserRecord = userRecord;
2434             mSystemProvider =
2435                     new SystemMediaRoute2Provider(
2436                             service.mContext, UserHandle.of(userRecord.mUserId), looper);
2437             mRouteProviders.add(mSystemProvider);
2438             mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
2439                     this, mUserRecord.mUserId);
2440         }
2441 
init()2442         void init() {
2443             mSystemProvider.setCallback(this);
2444         }
2445 
start()2446         private void start() {
2447             if (!mRunning) {
2448                 mRunning = true;
2449                 mSystemProvider.start();
2450                 mWatcher.start();
2451             }
2452         }
2453 
stop()2454         private void stop() {
2455             if (mRunning) {
2456                 mRunning = false;
2457                 mWatcher.stop(); // also stops all providers
2458                 mSystemProvider.stop();
2459             }
2460         }
2461 
2462         @Override
onAddProviderService(@onNull MediaRoute2ProviderServiceProxy proxy)2463         public void onAddProviderService(@NonNull MediaRoute2ProviderServiceProxy proxy) {
2464             proxy.setCallback(this);
2465             mRouteProviders.add(proxy);
2466             proxy.updateDiscoveryPreference(
2467                     mUserRecord.mActivelyScanningPackages,
2468                     mUserRecord.mCompositeDiscoveryPreference);
2469         }
2470 
2471         @Override
onRemoveProviderService(@onNull MediaRoute2ProviderServiceProxy proxy)2472         public void onRemoveProviderService(@NonNull MediaRoute2ProviderServiceProxy proxy) {
2473             mRouteProviders.remove(proxy);
2474         }
2475 
2476         @Override
onProviderStateChanged(@onNull MediaRoute2Provider provider)2477         public void onProviderStateChanged(@NonNull MediaRoute2Provider provider) {
2478             sendMessage(PooledLambda.obtainMessage(UserHandler::onProviderStateChangedOnHandler,
2479                     this, provider));
2480         }
2481 
2482         @Override
onSessionCreated(@onNull MediaRoute2Provider provider, long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo)2483         public void onSessionCreated(@NonNull MediaRoute2Provider provider,
2484                 long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo) {
2485             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreatedOnHandler,
2486                     this, provider, uniqueRequestId, sessionInfo));
2487         }
2488 
2489         @Override
onSessionUpdated(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)2490         public void onSessionUpdated(@NonNull MediaRoute2Provider provider,
2491                 @NonNull RoutingSessionInfo sessionInfo) {
2492             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler,
2493                     this, provider, sessionInfo));
2494         }
2495 
2496         @Override
onSessionReleased(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)2497         public void onSessionReleased(@NonNull MediaRoute2Provider provider,
2498                 @NonNull RoutingSessionInfo sessionInfo) {
2499             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionReleasedOnHandler,
2500                     this, provider, sessionInfo));
2501         }
2502 
2503         @Override
onRequestFailed(@onNull MediaRoute2Provider provider, long uniqueRequestId, int reason)2504         public void onRequestFailed(@NonNull MediaRoute2Provider provider, long uniqueRequestId,
2505                 int reason) {
2506             sendMessage(PooledLambda.obtainMessage(UserHandler::onRequestFailedOnHandler,
2507                     this, provider, uniqueRequestId, reason));
2508         }
2509 
2510         @GuardedBy("mLock")
2511         @Nullable
findRouterWithSessionLocked(@onNull String uniqueSessionId)2512         public RouterRecord findRouterWithSessionLocked(@NonNull String uniqueSessionId) {
2513             return mSessionToRouterMap.get(uniqueSessionId);
2514         }
2515 
2516         @Nullable
findManagerWithId(int managerId)2517         public ManagerRecord findManagerWithId(int managerId) {
2518             for (ManagerRecord manager : getManagerRecords()) {
2519                 if (manager.mManagerId == managerId) {
2520                     return manager;
2521                 }
2522             }
2523             return null;
2524         }
2525 
maybeUpdateDiscoveryPreferenceForUid(int uid)2526         public void maybeUpdateDiscoveryPreferenceForUid(int uid) {
2527             MediaRouter2ServiceImpl service = mServiceRef.get();
2528             if (service == null) {
2529                 return;
2530             }
2531             boolean isUidRelevant;
2532             synchronized (service.mLock) {
2533                 isUidRelevant =
2534                         mUserRecord.mRouterRecords.stream().anyMatch(router -> router.mUid == uid)
2535                                 | mUserRecord.mManagerRecords.stream()
2536                                         .anyMatch(manager -> manager.mOwnerUid == uid);
2537             }
2538             if (isUidRelevant) {
2539                 sendMessage(PooledLambda.obtainMessage(
2540                         UserHandler::updateDiscoveryPreferenceOnHandler, this));
2541             }
2542         }
2543 
dump(@onNull PrintWriter pw, @NonNull String prefix)2544         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
2545             pw.println(prefix + "UserHandler");
2546 
2547             String indent = prefix + "  ";
2548             pw.println(indent + "mRunning=" + mRunning);
2549 
2550             mSystemProvider.dump(pw, prefix);
2551             mWatcher.dump(pw, prefix);
2552         }
2553 
onProviderStateChangedOnHandler(@onNull MediaRoute2Provider provider)2554         private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
2555             MediaRoute2ProviderInfo newInfo = provider.getProviderInfo();
2556             int providerInfoIndex =
2557                     indexOfRouteProviderInfoByUniqueId(provider.getUniqueId(), mLastProviderInfos);
2558             MediaRoute2ProviderInfo oldInfo =
2559                     providerInfoIndex == -1 ? null : mLastProviderInfos.get(providerInfoIndex);
2560 
2561             if (oldInfo == newInfo) {
2562                 // Nothing to do.
2563                 return;
2564             }
2565 
2566             Collection<MediaRoute2Info> newRoutes;
2567             Set<String> newRouteIds;
2568             if (newInfo != null) {
2569                 // Adding or updating a provider.
2570                 newRoutes = newInfo.getRoutes();
2571                 newRouteIds =
2572                         newRoutes.stream().map(MediaRoute2Info::getId).collect(Collectors.toSet());
2573                 if (providerInfoIndex >= 0) {
2574                     mLastProviderInfos.set(providerInfoIndex, newInfo);
2575                 } else {
2576                     mLastProviderInfos.add(newInfo);
2577                 }
2578             } else /* newInfo == null */ {
2579                 // Removing a provider.
2580                 mLastProviderInfos.remove(oldInfo);
2581                 newRouteIds = Collections.emptySet();
2582                 newRoutes = Collections.emptySet();
2583             }
2584 
2585             // Add new routes to the maps.
2586             ArrayList<MediaRoute2Info> addedRoutes = new ArrayList<>();
2587             boolean hasAddedOrModifiedRoutes = false;
2588             for (MediaRoute2Info newRouteInfo : newRoutes) {
2589                 if (!newRouteInfo.isValid()) {
2590                     Slog.w(TAG, "onProviderStateChangedOnHandler: Ignoring invalid route : "
2591                             + newRouteInfo);
2592                     continue;
2593                 }
2594                 if (!provider.mIsSystemRouteProvider) {
2595                     mLastNotifiedRoutesToNonPrivilegedRouters.put(
2596                             newRouteInfo.getId(), newRouteInfo);
2597                 }
2598                 MediaRoute2Info oldRouteInfo =
2599                         mLastNotifiedRoutesToPrivilegedRouters.put(
2600                                 newRouteInfo.getId(), newRouteInfo);
2601                 hasAddedOrModifiedRoutes |= !newRouteInfo.equals(oldRouteInfo);
2602                 if (oldRouteInfo == null) {
2603                     addedRoutes.add(newRouteInfo);
2604                 }
2605             }
2606 
2607             // Remove stale routes from the maps.
2608             ArrayList<MediaRoute2Info> removedRoutes = new ArrayList<>();
2609             Collection<MediaRoute2Info> oldRoutes =
2610                     oldInfo == null ? Collections.emptyList() : oldInfo.getRoutes();
2611             boolean hasRemovedRoutes = false;
2612             for (MediaRoute2Info oldRoute : oldRoutes) {
2613                 String oldRouteId = oldRoute.getId();
2614                 if (!newRouteIds.contains(oldRouteId)) {
2615                     hasRemovedRoutes = true;
2616                     mLastNotifiedRoutesToPrivilegedRouters.remove(oldRouteId);
2617                     mLastNotifiedRoutesToNonPrivilegedRouters.remove(oldRouteId);
2618                     removedRoutes.add(oldRoute);
2619                 }
2620             }
2621 
2622             if (!addedRoutes.isEmpty()) {
2623                 // If routes were added, newInfo cannot be null.
2624                 Slog.i(TAG,
2625                         toLoggingMessage(
2626                                 /* source= */ "addProviderRoutes",
2627                                 newInfo.getUniqueId(),
2628                                 addedRoutes));
2629             }
2630             if (!removedRoutes.isEmpty()) {
2631                 // If routes were removed, oldInfo cannot be null.
2632                 Slog.i(TAG,
2633                         toLoggingMessage(
2634                                 /* source= */ "removeProviderRoutes",
2635                                 oldInfo.getUniqueId(),
2636                                 removedRoutes));
2637             }
2638 
2639             dispatchUpdates(
2640                     hasAddedOrModifiedRoutes,
2641                     hasRemovedRoutes,
2642                     provider.mIsSystemRouteProvider,
2643                     mSystemProvider.getDefaultRoute());
2644         }
2645 
getPackageNameFromNullableRecord( @ullable RouterRecord routerRecord)2646         private static String getPackageNameFromNullableRecord(
2647                 @Nullable RouterRecord routerRecord) {
2648             return routerRecord != null ? routerRecord.mPackageName : "<null router record>";
2649         }
2650 
toLoggingMessage( String source, String providerId, ArrayList<MediaRoute2Info> routes)2651         private static String toLoggingMessage(
2652                 String source, String providerId, ArrayList<MediaRoute2Info> routes) {
2653             String routesString =
2654                     routes.stream()
2655                             .map(it -> String.format("%s | %s", it.getOriginalId(), it.getName()))
2656                             .collect(Collectors.joining(/* delimiter= */ ", "));
2657             return TextUtils.formatSimple("%s | provider: %s, routes: [%s]",
2658                     source, providerId, routesString);
2659         }
2660 
2661         /**
2662          * Dispatches the latest route updates in {@link #mLastNotifiedRoutesToPrivilegedRouters}
2663          * and {@link #mLastNotifiedRoutesToNonPrivilegedRouters} to registered {@link
2664          * android.media.MediaRouter2 routers} and {@link MediaRouter2Manager managers} after a call
2665          * to {@link #onProviderStateChangedOnHandler(MediaRoute2Provider)}. Ignores if no changes
2666          * were made.
2667          *
2668          * @param hasAddedOrModifiedRoutes whether routes were added or modified.
2669          * @param hasRemovedRoutes whether routes were removed.
2670          * @param isSystemProvider whether the latest update was caused by a system provider.
2671          * @param defaultRoute the current default route in {@link #mSystemProvider}.
2672          */
dispatchUpdates( boolean hasAddedOrModifiedRoutes, boolean hasRemovedRoutes, boolean isSystemProvider, MediaRoute2Info defaultRoute)2673         private void dispatchUpdates(
2674                 boolean hasAddedOrModifiedRoutes,
2675                 boolean hasRemovedRoutes,
2676                 boolean isSystemProvider,
2677                 MediaRoute2Info defaultRoute) {
2678 
2679             // Ignore if no changes.
2680             if (!hasAddedOrModifiedRoutes && !hasRemovedRoutes) {
2681                 return;
2682             }
2683             List<RouterRecord> routerRecordsWithSystemRoutingPermission =
2684                     getRouterRecords(/* hasSystemRoutingPermission= */ true);
2685             List<RouterRecord> routerRecordsWithoutSystemRoutingPermission =
2686                     getRouterRecords(/* hasSystemRoutingPermission= */ false);
2687             List<IMediaRouter2Manager> managers = getManagers();
2688 
2689             // Managers receive all provider updates with all routes.
2690             notifyRoutesUpdatedToManagers(
2691                     managers, new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
2692 
2693             // Routers with system routing access (either via {@link MODIFY_AUDIO_ROUTING} or
2694             // {@link BLUETOOTH_CONNECT} + {@link BLUETOOTH_SCAN}) receive all provider updates
2695             // with all routes.
2696             notifyRoutesUpdatedToRouterRecords(
2697                     routerRecordsWithSystemRoutingPermission,
2698                     new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
2699 
2700             if (!isSystemProvider) {
2701                 // Regular routers receive updates from all non-system providers with all non-system
2702                 // routes.
2703                 notifyRoutesUpdatedToRouterRecords(
2704                         routerRecordsWithoutSystemRoutingPermission,
2705                         new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
2706             } else if (hasAddedOrModifiedRoutes) {
2707                 // On system provider updates, routers without system routing access
2708                 // receive the updated default route. This is the only system route they should
2709                 // receive.
2710                 mLastNotifiedRoutesToNonPrivilegedRouters.put(defaultRoute.getId(), defaultRoute);
2711                 notifyRoutesUpdatedToRouterRecords(
2712                         routerRecordsWithoutSystemRoutingPermission,
2713                         new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
2714             }
2715         }
2716 
2717         /**
2718          * Returns the index of the first element in {@code lastProviderInfos} that matches the
2719          * specified unique id.
2720          *
2721          * @param uniqueId unique id of {@link MediaRoute2ProviderInfo} to be found.
2722          * @param lastProviderInfos list of {@link MediaRoute2ProviderInfo}.
2723          * @return index of found element, or -1 if not found.
2724          */
indexOfRouteProviderInfoByUniqueId( @onNull String uniqueId, @NonNull List<MediaRoute2ProviderInfo> lastProviderInfos)2725         private static int indexOfRouteProviderInfoByUniqueId(
2726                 @NonNull String uniqueId,
2727                 @NonNull List<MediaRoute2ProviderInfo> lastProviderInfos) {
2728             for (int i = 0; i < lastProviderInfos.size(); i++) {
2729                 MediaRoute2ProviderInfo providerInfo = lastProviderInfos.get(i);
2730                 if (TextUtils.equals(providerInfo.getUniqueId(), uniqueId)) {
2731                     return i;
2732                 }
2733             }
2734             return -1;
2735         }
2736 
requestRouterCreateSessionOnHandler( long uniqueRequestId, @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName)2737         private void requestRouterCreateSessionOnHandler(
2738                 long uniqueRequestId,
2739                 @NonNull RouterRecord routerRecord,
2740                 @NonNull ManagerRecord managerRecord,
2741                 @NonNull RoutingSessionInfo oldSession,
2742                 @NonNull MediaRoute2Info route,
2743                 @NonNull UserHandle transferInitiatorUserHandle,
2744                 @NonNull String transferInitiatorPackageName) {
2745             try {
2746                 if (route.isSystemRoute() && !routerRecord.hasSystemRoutingPermission()) {
2747                     // The router lacks permission to modify system routing, so we hide system
2748                     // route info from them.
2749                     route = mSystemProvider.getDefaultRoute();
2750                 }
2751                 routerRecord.mRouter.requestCreateSessionByManager(
2752                         uniqueRequestId, oldSession, route);
2753             } catch (RemoteException ex) {
2754                 Slog.w(TAG, "getSessionHintsForCreatingSessionOnHandler: "
2755                         + "Failed to request. Router probably died.", ex);
2756                 notifyRequestFailedToManager(managerRecord.mManager,
2757                         toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR);
2758             }
2759         }
2760 
requestCreateSessionWithRouter2OnHandler( long uniqueRequestId, long managerRequestId, @NonNull RouterRecord routerRecord, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints)2761         private void requestCreateSessionWithRouter2OnHandler(
2762                 long uniqueRequestId,
2763                 long managerRequestId,
2764                 @NonNull RouterRecord routerRecord,
2765                 @NonNull RoutingSessionInfo oldSession,
2766                 @NonNull MediaRoute2Info route,
2767                 @Nullable Bundle sessionHints) {
2768 
2769             final MediaRoute2Provider provider = findProvider(route.getProviderId());
2770             if (provider == null) {
2771                 Slog.w(TAG, "requestCreateSessionWithRouter2OnHandler: Ignoring session "
2772                         + "creation request since no provider found for given route=" + route);
2773                 notifySessionCreationFailedToRouter(routerRecord,
2774                         toOriginalRequestId(uniqueRequestId));
2775                 return;
2776             }
2777 
2778             SessionCreationRequest request =
2779                     new SessionCreationRequest(routerRecord, uniqueRequestId,
2780                             managerRequestId, oldSession, route);
2781             mSessionCreationRequests.add(request);
2782 
2783             int transferReason =
2784                     managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE
2785                             ? RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST
2786                             : RoutingSessionInfo.TRANSFER_REASON_APP;
2787 
2788             provider.requestCreateSession(
2789                     uniqueRequestId,
2790                     routerRecord.mPackageName,
2791                     route.getOriginalId(),
2792                     sessionHints,
2793                     transferReason,
2794                     UserHandle.of(routerRecord.mUserRecord.mUserId),
2795                     routerRecord.mPackageName);
2796         }
2797 
2798         // routerRecord can be null if the session is system's or RCN.
selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)2799         private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord,
2800                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
2801             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
2802                     "selecting")) {
2803                 return;
2804             }
2805 
2806             final String providerId = route.getProviderId();
2807             final MediaRoute2Provider provider = findProvider(providerId);
2808             if (provider == null) {
2809                 return;
2810             }
2811             provider.selectRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
2812                     route.getOriginalId());
2813         }
2814 
2815         // routerRecord can be null if the session is system's or RCN.
deselectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)2816         private void deselectRouteOnHandler(long uniqueRequestId,
2817                 @Nullable RouterRecord routerRecord,
2818                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
2819             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
2820                     "deselecting")) {
2821                 return;
2822             }
2823 
2824             final String providerId = route.getProviderId();
2825             final MediaRoute2Provider provider = findProvider(providerId);
2826             if (provider == null) {
2827                 return;
2828             }
2829 
2830             provider.deselectRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
2831                     route.getOriginalId());
2832         }
2833 
2834         // routerRecord can be null if the session is system's or RCN.
transferToRouteOnHandler( long uniqueRequestId, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, @RoutingSessionInfo.TransferReason int transferReason)2835         private void transferToRouteOnHandler(
2836                 long uniqueRequestId,
2837                 @NonNull UserHandle transferInitiatorUserHandle,
2838                 @NonNull String transferInitiatorPackageName,
2839                 @Nullable RouterRecord routerRecord,
2840                 @NonNull String uniqueSessionId,
2841                 @NonNull MediaRoute2Info route,
2842                 @RoutingSessionInfo.TransferReason int transferReason) {
2843             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
2844                     "transferring to")) {
2845                 return;
2846             }
2847 
2848             final String providerId = route.getProviderId();
2849             final MediaRoute2Provider provider = findProvider(providerId);
2850             if (provider == null) {
2851                 Slog.w(
2852                         TAG,
2853                         "Ignoring transferToRoute due to lack of matching provider for target: "
2854                                 + route);
2855                 return;
2856             }
2857             provider.transferToRoute(
2858                     uniqueRequestId,
2859                     transferInitiatorUserHandle,
2860                     transferInitiatorPackageName,
2861                     getOriginalId(uniqueSessionId),
2862                     route.getOriginalId(),
2863                     transferReason);
2864         }
2865 
2866         // routerRecord is null if and only if the session is created without the request, which
2867         // includes the system's session and RCN cases.
checkArgumentsForSessionControl(@ullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, @NonNull String description)2868         private boolean checkArgumentsForSessionControl(@Nullable RouterRecord routerRecord,
2869                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route,
2870                 @NonNull String description) {
2871             final String providerId = route.getProviderId();
2872             final MediaRoute2Provider provider = findProvider(providerId);
2873             if (provider == null) {
2874                 Slog.w(TAG, "Ignoring " + description + " route since no provider found for "
2875                         + "given route=" + route);
2876                 return false;
2877             }
2878 
2879             // Bypass checking router if it's the system session (routerRecord should be null)
2880             if (TextUtils.equals(getProviderId(uniqueSessionId), mSystemProvider.getUniqueId())) {
2881                 return true;
2882             }
2883 
2884             RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
2885             if (matchingRecord != routerRecord) {
2886                 Slog.w(
2887                         TAG,
2888                         "Ignoring "
2889                                 + description
2890                                 + " route from non-matching router."
2891                                 + " routerRecordPackageName="
2892                                 + getPackageNameFromNullableRecord(routerRecord)
2893                                 + " matchingRecordPackageName="
2894                                 + getPackageNameFromNullableRecord(matchingRecord)
2895                                 + " route="
2896                                 + route);
2897                 return false;
2898             }
2899 
2900             final String sessionId = getOriginalId(uniqueSessionId);
2901             if (sessionId == null) {
2902                 Slog.w(TAG, "Failed to get original session id from unique session id. "
2903                         + "uniqueSessionId=" + uniqueSessionId);
2904                 return false;
2905             }
2906 
2907             return true;
2908         }
2909 
setRouteVolumeOnHandler(long uniqueRequestId, @NonNull MediaRoute2Info route, int volume)2910         private void setRouteVolumeOnHandler(long uniqueRequestId, @NonNull MediaRoute2Info route,
2911                 int volume) {
2912             final MediaRoute2Provider provider = findProvider(route.getProviderId());
2913             if (provider == null) {
2914                 Slog.w(TAG, "setRouteVolumeOnHandler: Couldn't find provider for route=" + route);
2915                 return;
2916             }
2917             provider.setRouteVolume(uniqueRequestId, route.getOriginalId(), volume);
2918         }
2919 
setSessionVolumeOnHandler(long uniqueRequestId, @NonNull String uniqueSessionId, int volume)2920         private void setSessionVolumeOnHandler(long uniqueRequestId,
2921                 @NonNull String uniqueSessionId, int volume) {
2922             final MediaRoute2Provider provider = findProvider(getProviderId(uniqueSessionId));
2923             if (provider == null) {
2924                 Slog.w(TAG, "setSessionVolumeOnHandler: Couldn't find provider for session id="
2925                         + uniqueSessionId);
2926                 return;
2927             }
2928             provider.setSessionVolume(uniqueRequestId, getOriginalId(uniqueSessionId), volume);
2929         }
2930 
releaseSessionOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId)2931         private void releaseSessionOnHandler(long uniqueRequestId,
2932                 @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId) {
2933             final RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
2934             if (matchingRecord != routerRecord) {
2935                 Slog.w(
2936                         TAG,
2937                         "Ignoring releasing session from non-matching router."
2938                                 + " routerRecordPackageName="
2939                                 + getPackageNameFromNullableRecord(routerRecord)
2940                                 + " matchingRecordPackageName="
2941                                 + getPackageNameFromNullableRecord(matchingRecord)
2942                                 + " uniqueSessionId="
2943                                 + uniqueSessionId);
2944                 return;
2945             }
2946 
2947             final String providerId = getProviderId(uniqueSessionId);
2948             if (providerId == null) {
2949                 Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
2950                         + "uniqueSessionId=" + uniqueSessionId);
2951                 return;
2952             }
2953 
2954             final String sessionId = getOriginalId(uniqueSessionId);
2955             if (sessionId == null) {
2956                 Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
2957                         + "uniqueSessionId=" + uniqueSessionId + " providerId=" + providerId);
2958                 return;
2959             }
2960 
2961             final MediaRoute2Provider provider = findProvider(providerId);
2962             if (provider == null) {
2963                 Slog.w(TAG, "Ignoring releasing session since no provider found for given "
2964                         + "providerId=" + providerId);
2965                 return;
2966             }
2967 
2968             provider.releaseSession(uniqueRequestId, sessionId);
2969         }
2970 
onSessionCreatedOnHandler(@onNull MediaRoute2Provider provider, long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo)2971         private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
2972                 long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo) {
2973             SessionCreationRequest matchingRequest = null;
2974 
2975             for (SessionCreationRequest request : mSessionCreationRequests) {
2976                 if (request.mUniqueRequestId == uniqueRequestId
2977                         && TextUtils.equals(
2978                         request.mRoute.getProviderId(), provider.getUniqueId())) {
2979                     matchingRequest = request;
2980                     break;
2981                 }
2982             }
2983 
2984             long managerRequestId = (matchingRequest == null)
2985                     ? MediaRoute2ProviderService.REQUEST_ID_NONE
2986                     : matchingRequest.mManagerRequestId;
2987             notifySessionCreatedToManagers(managerRequestId, sessionInfo);
2988 
2989             if (matchingRequest == null) {
2990                 Slog.w(TAG, "Ignoring session creation result for unknown request. "
2991                         + "uniqueRequestId=" + uniqueRequestId + ", sessionInfo=" + sessionInfo);
2992                 return;
2993             }
2994 
2995             mSessionCreationRequests.remove(matchingRequest);
2996             // Not to show old session
2997             MediaRoute2Provider oldProvider =
2998                     findProvider(matchingRequest.mOldSession.getProviderId());
2999             if (oldProvider != null) {
3000                 oldProvider.prepareReleaseSession(matchingRequest.mOldSession.getId());
3001             } else {
3002                 Slog.w(TAG, "onSessionCreatedOnHandler: Can't find provider for an old session. "
3003                         + "session=" + matchingRequest.mOldSession);
3004             }
3005 
3006             mSessionToRouterMap.put(sessionInfo.getId(), matchingRequest.mRouterRecord);
3007             if (sessionInfo.isSystemSession()
3008                     && !matchingRequest.mRouterRecord.hasSystemRoutingPermission()) {
3009                 // The router lacks permission to modify system routing, so we hide system routing
3010                 // session info from them.
3011                 sessionInfo = mSystemProvider.getDefaultSessionInfo();
3012             }
3013             matchingRequest.mRouterRecord.notifySessionCreated(
3014                     toOriginalRequestId(uniqueRequestId), sessionInfo);
3015         }
3016 
onSessionInfoChangedOnHandler(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)3017         private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
3018                 @NonNull RoutingSessionInfo sessionInfo) {
3019             List<IMediaRouter2Manager> managers = getManagers();
3020             notifySessionUpdatedToManagers(managers, sessionInfo);
3021 
3022             // For system provider, notify all routers.
3023             if (provider == mSystemProvider) {
3024                 if (mServiceRef.get() == null) {
3025                     return;
3026                 }
3027                 notifySessionInfoChangedToRouters(getRouterRecords(true), sessionInfo);
3028                 notifySessionInfoChangedToRouters(
3029                         getRouterRecords(false), mSystemProvider.getDefaultSessionInfo());
3030                 return;
3031             }
3032 
3033             RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId());
3034             if (routerRecord == null) {
3035                 Slog.w(TAG, "onSessionInfoChangedOnHandler: No matching router found for session="
3036                         + sessionInfo);
3037                 return;
3038             }
3039             notifySessionInfoChangedToRouters(Arrays.asList(routerRecord), sessionInfo);
3040         }
3041 
onSessionReleasedOnHandler(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)3042         private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
3043                 @NonNull RoutingSessionInfo sessionInfo) {
3044             List<IMediaRouter2Manager> managers = getManagers();
3045             notifySessionReleasedToManagers(managers, sessionInfo);
3046 
3047             RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId());
3048             if (routerRecord == null) {
3049                 Slog.w(TAG, "onSessionReleasedOnHandler: No matching router found for session="
3050                         + sessionInfo);
3051                 return;
3052             }
3053             notifySessionReleasedToRouter(routerRecord, sessionInfo);
3054         }
3055 
onRequestFailedOnHandler(@onNull MediaRoute2Provider provider, long uniqueRequestId, int reason)3056         private void onRequestFailedOnHandler(@NonNull MediaRoute2Provider provider,
3057                 long uniqueRequestId, int reason) {
3058             if (handleSessionCreationRequestFailed(provider, uniqueRequestId, reason)) {
3059                 Slog.w(
3060                         TAG,
3061                         TextUtils.formatSimple(
3062                                 "onRequestFailedOnHandler | Finished handling session creation"
3063                                     + " request failed for provider: %s, uniqueRequestId: %d,"
3064                                     + " reason: %d",
3065                                 provider.getUniqueId(), uniqueRequestId, reason));
3066                 return;
3067             }
3068 
3069             final int requesterId = toRequesterId(uniqueRequestId);
3070             ManagerRecord manager = findManagerWithId(requesterId);
3071             if (manager != null) {
3072                 notifyRequestFailedToManager(
3073                         manager.mManager, toOriginalRequestId(uniqueRequestId), reason);
3074             }
3075 
3076             // Currently, only manager records can get notified of failures.
3077             // TODO(b/282936553): Notify regular routers of request failures.
3078         }
3079 
handleSessionCreationRequestFailed(@onNull MediaRoute2Provider provider, long uniqueRequestId, int reason)3080         private boolean handleSessionCreationRequestFailed(@NonNull MediaRoute2Provider provider,
3081                 long uniqueRequestId, int reason) {
3082             // Check whether the failure is about creating a session
3083             SessionCreationRequest matchingRequest = null;
3084             for (SessionCreationRequest request : mSessionCreationRequests) {
3085                 if (request.mUniqueRequestId == uniqueRequestId && TextUtils.equals(
3086                         request.mRoute.getProviderId(), provider.getUniqueId())) {
3087                     matchingRequest = request;
3088                     break;
3089                 }
3090             }
3091 
3092             if (matchingRequest == null) {
3093                 // The failure is not about creating a session.
3094                 Slog.w(
3095                         TAG,
3096                         TextUtils.formatSimple(
3097                                 "handleSessionCreationRequestFailed | No matching request found for"
3098                                     + " provider: %s, uniqueRequestId: %d, reason: %d",
3099                                 provider.getUniqueId(), uniqueRequestId, reason));
3100                 return false;
3101             }
3102 
3103             mSessionCreationRequests.remove(matchingRequest);
3104 
3105             // Notify the requester about the failure.
3106             // The call should be made by either MediaRouter2 or MediaRouter2Manager.
3107             if (matchingRequest.mManagerRequestId == MediaRouter2Manager.REQUEST_ID_NONE) {
3108                 notifySessionCreationFailedToRouter(
3109                         matchingRequest.mRouterRecord, toOriginalRequestId(uniqueRequestId));
3110             } else {
3111                 final int requesterId = toRequesterId(matchingRequest.mManagerRequestId);
3112                 ManagerRecord manager = findManagerWithId(requesterId);
3113                 if (manager != null) {
3114                     notifyRequestFailedToManager(manager.mManager,
3115                             toOriginalRequestId(matchingRequest.mManagerRequestId), reason);
3116                 }
3117             }
3118             return true;
3119         }
3120 
notifySessionCreationFailedToRouter(@onNull RouterRecord routerRecord, int requestId)3121         private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
3122                 int requestId) {
3123             try {
3124                 routerRecord.mRouter.notifySessionCreated(requestId,
3125                         /* sessionInfo= */ null);
3126             } catch (RemoteException ex) {
3127                 Slog.w(TAG, "Failed to notify router of the session creation failure."
3128                         + " Router probably died.", ex);
3129             }
3130         }
3131 
notifySessionReleasedToRouter(@onNull RouterRecord routerRecord, @NonNull RoutingSessionInfo sessionInfo)3132         private void notifySessionReleasedToRouter(@NonNull RouterRecord routerRecord,
3133                 @NonNull RoutingSessionInfo sessionInfo) {
3134             try {
3135                 routerRecord.mRouter.notifySessionReleased(sessionInfo);
3136             } catch (RemoteException ex) {
3137                 Slog.w(TAG, "Failed to notify router of the session release."
3138                         + " Router probably died.", ex);
3139             }
3140         }
3141 
getManagers()3142         private List<IMediaRouter2Manager> getManagers() {
3143             final List<IMediaRouter2Manager> managers = new ArrayList<>();
3144             MediaRouter2ServiceImpl service = mServiceRef.get();
3145             if (service == null) {
3146                 return managers;
3147             }
3148             synchronized (service.mLock) {
3149                 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
3150                     managers.add(managerRecord.mManager);
3151                 }
3152             }
3153             return managers;
3154         }
3155 
getRouterRecords()3156         private List<RouterRecord> getRouterRecords() {
3157             MediaRouter2ServiceImpl service = mServiceRef.get();
3158             if (service == null) {
3159                 return Collections.emptyList();
3160             }
3161             synchronized (service.mLock) {
3162                 return new ArrayList<>(mUserRecord.mRouterRecords);
3163             }
3164         }
3165 
getRouterRecords(boolean hasSystemRoutingPermission)3166         private List<RouterRecord> getRouterRecords(boolean hasSystemRoutingPermission) {
3167             MediaRouter2ServiceImpl service = mServiceRef.get();
3168             List<RouterRecord> routerRecords = new ArrayList<>();
3169             if (service == null) {
3170                 return routerRecords;
3171             }
3172             synchronized (service.mLock) {
3173                 for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
3174                     if (hasSystemRoutingPermission
3175                             == routerRecord.hasSystemRoutingPermission()) {
3176                         routerRecords.add(routerRecord);
3177                     }
3178                 }
3179                 return routerRecords;
3180             }
3181         }
3182 
getManagerRecords()3183         private List<ManagerRecord> getManagerRecords() {
3184             MediaRouter2ServiceImpl service = mServiceRef.get();
3185             if (service == null) {
3186                 return Collections.emptyList();
3187             }
3188             synchronized (service.mLock) {
3189                 return new ArrayList<>(mUserRecord.mManagerRecords);
3190             }
3191         }
3192 
notifyRouterRegistered(@onNull RouterRecord routerRecord)3193         private void notifyRouterRegistered(@NonNull RouterRecord routerRecord) {
3194             List<MediaRoute2Info> currentRoutes = new ArrayList<>();
3195 
3196             MediaRoute2ProviderInfo systemProviderInfo = null;
3197             for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
3198                 // TODO: Create MediaRoute2ProviderInfo#isSystemProvider()
3199                 if (TextUtils.equals(providerInfo.getUniqueId(), mSystemProvider.getUniqueId())) {
3200                     // Adding routes from system provider will be handled below, so skip it here.
3201                     systemProviderInfo = providerInfo;
3202                     continue;
3203                 }
3204                 currentRoutes.addAll(providerInfo.getRoutes());
3205             }
3206 
3207             RoutingSessionInfo currentSystemSessionInfo;
3208             if (routerRecord.hasSystemRoutingPermission()) {
3209                 if (systemProviderInfo != null) {
3210                     currentRoutes.addAll(systemProviderInfo.getRoutes());
3211                 } else {
3212                     // This shouldn't happen.
3213                     Slog.wtf(TAG, "System route provider not found.");
3214                 }
3215                 currentSystemSessionInfo = mSystemProvider.getSessionInfos().get(0);
3216             } else {
3217                 currentRoutes.add(mSystemProvider.getDefaultRoute());
3218                 currentSystemSessionInfo = mSystemProvider.getDefaultSessionInfo();
3219             }
3220 
3221             if (!currentRoutes.isEmpty()) {
3222                 routerRecord.notifyRegistered(currentRoutes, currentSystemSessionInfo);
3223             }
3224         }
3225 
notifyRoutesUpdatedToRouterRecords( @onNull List<RouterRecord> routerRecords, @NonNull List<MediaRoute2Info> routes)3226         private static void notifyRoutesUpdatedToRouterRecords(
3227                 @NonNull List<RouterRecord> routerRecords,
3228                 @NonNull List<MediaRoute2Info> routes) {
3229             for (RouterRecord routerRecord : routerRecords) {
3230                 routerRecord.notifyRoutesUpdated(routes);
3231             }
3232         }
3233 
notifySessionInfoChangedToRouters( @onNull List<RouterRecord> routerRecords, @NonNull RoutingSessionInfo sessionInfo)3234         private void notifySessionInfoChangedToRouters(
3235                 @NonNull List<RouterRecord> routerRecords,
3236                 @NonNull RoutingSessionInfo sessionInfo) {
3237             for (RouterRecord routerRecord : routerRecords) {
3238                 routerRecord.notifySessionInfoChanged(sessionInfo);
3239             }
3240         }
3241 
3242         /**
3243          * Notifies {@code manager} with all known routes. This only happens once after {@code
3244          * manager} is registered through {@link #registerManager(IMediaRouter2Manager, String)
3245          * registerManager()}.
3246          *
3247          * @param manager {@link IMediaRouter2Manager} to be notified.
3248          */
notifyInitialRoutesToManager(@onNull IMediaRouter2Manager manager)3249         private void notifyInitialRoutesToManager(@NonNull IMediaRouter2Manager manager) {
3250             if (mLastNotifiedRoutesToPrivilegedRouters.isEmpty()) {
3251                 return;
3252             }
3253             try {
3254                 manager.notifyRoutesUpdated(
3255                         new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
3256             } catch (RemoteException ex) {
3257                 Slog.w(TAG, "Failed to notify all routes. Manager probably died.", ex);
3258             }
3259         }
3260 
notifyRoutesUpdatedToManagers( @onNull List<IMediaRouter2Manager> managers, @NonNull List<MediaRoute2Info> routes)3261         private void notifyRoutesUpdatedToManagers(
3262                 @NonNull List<IMediaRouter2Manager> managers,
3263                 @NonNull List<MediaRoute2Info> routes) {
3264             for (IMediaRouter2Manager manager : managers) {
3265                 try {
3266                     manager.notifyRoutesUpdated(routes);
3267                 } catch (RemoteException ex) {
3268                     Slog.w(TAG, "Failed to notify routes changed. Manager probably died.", ex);
3269                 }
3270             }
3271         }
3272 
notifySessionCreatedToManagers(long managerRequestId, @NonNull RoutingSessionInfo session)3273         private void notifySessionCreatedToManagers(long managerRequestId,
3274                 @NonNull RoutingSessionInfo session) {
3275             int requesterId = toRequesterId(managerRequestId);
3276             int originalRequestId = toOriginalRequestId(managerRequestId);
3277 
3278             for (ManagerRecord manager : getManagerRecords()) {
3279                 try {
3280                     manager.mManager.notifySessionCreated(
3281                             ((manager.mManagerId == requesterId) ? originalRequestId :
3282                                     MediaRouter2Manager.REQUEST_ID_NONE), session);
3283                 } catch (RemoteException ex) {
3284                     Slog.w(TAG, "notifySessionCreatedToManagers: "
3285                             + "Failed to notify. Manager probably died.", ex);
3286                 }
3287             }
3288         }
3289 
notifySessionUpdatedToManagers( @onNull List<IMediaRouter2Manager> managers, @NonNull RoutingSessionInfo sessionInfo)3290         private void notifySessionUpdatedToManagers(
3291                 @NonNull List<IMediaRouter2Manager> managers,
3292                 @NonNull RoutingSessionInfo sessionInfo) {
3293             for (IMediaRouter2Manager manager : managers) {
3294                 try {
3295                     manager.notifySessionUpdated(sessionInfo);
3296                 } catch (RemoteException ex) {
3297                     Slog.w(TAG, "notifySessionUpdatedToManagers: "
3298                             + "Failed to notify. Manager probably died.", ex);
3299                 }
3300             }
3301         }
3302 
notifySessionReleasedToManagers( @onNull List<IMediaRouter2Manager> managers, @NonNull RoutingSessionInfo sessionInfo)3303         private void notifySessionReleasedToManagers(
3304                 @NonNull List<IMediaRouter2Manager> managers,
3305                 @NonNull RoutingSessionInfo sessionInfo) {
3306             for (IMediaRouter2Manager manager : managers) {
3307                 try {
3308                     manager.notifySessionReleased(sessionInfo);
3309                 } catch (RemoteException ex) {
3310                     Slog.w(TAG, "notifySessionReleasedToManagers: "
3311                             + "Failed to notify. Manager probably died.", ex);
3312                 }
3313             }
3314         }
3315 
notifyDiscoveryPreferenceChangedToManager(@onNull RouterRecord routerRecord, @NonNull IMediaRouter2Manager manager)3316         private void notifyDiscoveryPreferenceChangedToManager(@NonNull RouterRecord routerRecord,
3317                 @NonNull IMediaRouter2Manager manager) {
3318             try {
3319                 manager.notifyDiscoveryPreferenceChanged(routerRecord.mPackageName,
3320                         routerRecord.mDiscoveryPreference);
3321             } catch (RemoteException ex) {
3322                 Slog.w(TAG, "Failed to notify preferred features changed."
3323                         + " Manager probably died.", ex);
3324             }
3325         }
3326 
notifyDiscoveryPreferenceChangedToManagers(@onNull String routerPackageName, @Nullable RouteDiscoveryPreference discoveryPreference)3327         private void notifyDiscoveryPreferenceChangedToManagers(@NonNull String routerPackageName,
3328                 @Nullable RouteDiscoveryPreference discoveryPreference) {
3329             MediaRouter2ServiceImpl service = mServiceRef.get();
3330             if (service == null) {
3331                 return;
3332             }
3333             List<IMediaRouter2Manager> managers = new ArrayList<>();
3334             synchronized (service.mLock) {
3335                 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
3336                     managers.add(managerRecord.mManager);
3337                 }
3338             }
3339             for (IMediaRouter2Manager manager : managers) {
3340                 try {
3341                     manager.notifyDiscoveryPreferenceChanged(routerPackageName,
3342                             discoveryPreference);
3343                 } catch (RemoteException ex) {
3344                     Slog.w(TAG, "Failed to notify preferred features changed."
3345                             + " Manager probably died.", ex);
3346                 }
3347             }
3348         }
3349 
notifyRouteListingPreferenceChangeToManagers( String routerPackageName, @Nullable RouteListingPreference routeListingPreference)3350         private void notifyRouteListingPreferenceChangeToManagers(
3351                 String routerPackageName, @Nullable RouteListingPreference routeListingPreference) {
3352             MediaRouter2ServiceImpl service = mServiceRef.get();
3353             if (service == null) {
3354                 return;
3355             }
3356             List<IMediaRouter2Manager> managers = new ArrayList<>();
3357             synchronized (service.mLock) {
3358                 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
3359                     managers.add(managerRecord.mManager);
3360                 }
3361             }
3362             for (IMediaRouter2Manager manager : managers) {
3363                 try {
3364                     manager.notifyRouteListingPreferenceChange(
3365                             routerPackageName, routeListingPreference);
3366                 } catch (RemoteException ex) {
3367                     Slog.w(
3368                             TAG,
3369                             "Failed to notify preferred features changed."
3370                                     + " Manager probably died.",
3371                             ex);
3372                 }
3373             }
3374             // TODO(b/238178508): In order to support privileged media router instances, we also
3375             //    need to update routers other than the one making the update.
3376         }
3377 
notifyRequestFailedToManager(@onNull IMediaRouter2Manager manager, int requestId, int reason)3378         private void notifyRequestFailedToManager(@NonNull IMediaRouter2Manager manager,
3379                 int requestId, int reason) {
3380             try {
3381                 manager.notifyRequestFailed(requestId, reason);
3382             } catch (RemoteException ex) {
3383                 Slog.w(TAG, "Failed to notify manager of the request failure."
3384                         + " Manager probably died.", ex);
3385             }
3386         }
3387 
updateDiscoveryPreferenceOnHandler()3388         private void updateDiscoveryPreferenceOnHandler() {
3389             MediaRouter2ServiceImpl service = mServiceRef.get();
3390             if (service == null) {
3391                 return;
3392             }
3393             List<RouterRecord> activeRouterRecords;
3394             List<RouterRecord> allRouterRecords = getRouterRecords();
3395 
3396             boolean areManagersScanning = areManagersScanning(service, getManagerRecords());
3397 
3398             if (areManagersScanning) {
3399                 activeRouterRecords = allRouterRecords;
3400             } else {
3401                 activeRouterRecords = getIndividuallyActiveRouters(service, allRouterRecords);
3402             }
3403 
3404             updateManagerScanningForProviders(areManagersScanning);
3405 
3406             Set<String> activelyScanningPackages = new HashSet<>();
3407             RouteDiscoveryPreference newPreference =
3408                     buildCompositeDiscoveryPreference(
3409                             activeRouterRecords, areManagersScanning, activelyScanningPackages);
3410 
3411             Slog.i(
3412                     TAG,
3413                     TextUtils.formatSimple(
3414                             "Updating composite discovery preference | preference: %s, active"
3415                                     + " routers: %s",
3416                             newPreference, activelyScanningPackages));
3417 
3418             if (updateScanningOnUserRecord(service, activelyScanningPackages, newPreference)) {
3419                 updateDiscoveryPreferenceForProviders(activelyScanningPackages);
3420             }
3421         }
3422 
updateDiscoveryPreferenceForProviders(Set<String> activelyScanningPackages)3423         private void updateDiscoveryPreferenceForProviders(Set<String> activelyScanningPackages) {
3424             for (MediaRoute2Provider provider : mRouteProviders) {
3425                 provider.updateDiscoveryPreference(
3426                         activelyScanningPackages, mUserRecord.mCompositeDiscoveryPreference);
3427             }
3428         }
3429 
updateScanningOnUserRecord( MediaRouter2ServiceImpl service, Set<String> activelyScanningPackages, RouteDiscoveryPreference newPreference)3430         private boolean updateScanningOnUserRecord(
3431                 MediaRouter2ServiceImpl service,
3432                 Set<String> activelyScanningPackages,
3433                 RouteDiscoveryPreference newPreference) {
3434             synchronized (service.mLock) {
3435                 if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)
3436                         && activelyScanningPackages.equals(mUserRecord.mActivelyScanningPackages)) {
3437                     return false;
3438                 }
3439                 mUserRecord.mCompositeDiscoveryPreference = newPreference;
3440                 mUserRecord.mActivelyScanningPackages = activelyScanningPackages;
3441             }
3442             return true;
3443         }
3444 
3445         /**
3446          * Returns a composite {@link RouteDiscoveryPreference} that aggregates every router
3447          * record's individual discovery preference.
3448          *
3449          * <p>The {@link RouteDiscoveryPreference#shouldPerformActiveScan() active scan value} of
3450          * the composite discovery preference is true if one of the router records is actively
3451          * scanning or if {@code shouldForceActiveScan} is true.
3452          *
3453          * <p>The composite RouteDiscoveryPreference is used to query route providers once to obtain
3454          * all the routes of interest, which can be subsequently filtered for the individual
3455          * discovery preferences.
3456          */
3457         @NonNull
buildCompositeDiscoveryPreference( List<RouterRecord> activeRouterRecords, boolean shouldForceActiveScan, Set<String> activelyScanningPackages)3458         private static RouteDiscoveryPreference buildCompositeDiscoveryPreference(
3459                 List<RouterRecord> activeRouterRecords,
3460                 boolean shouldForceActiveScan,
3461                 Set<String> activelyScanningPackages) {
3462             Set<String> preferredFeatures = new HashSet<>();
3463             boolean activeScan = false;
3464             for (RouterRecord activeRouterRecord : activeRouterRecords) {
3465                 RouteDiscoveryPreference preference = activeRouterRecord.mDiscoveryPreference;
3466                 preferredFeatures.addAll(preference.getPreferredFeatures());
3467 
3468                 boolean isRouterRecordActivelyScanning =
3469                         Flags.enablePreventionOfManagerScansWhenNoAppsScan()
3470                                 ? (activeRouterRecord.isActivelyScanning() || shouldForceActiveScan)
3471                                         && !preference.getPreferredFeatures().isEmpty()
3472                                 : activeRouterRecord.isActivelyScanning();
3473 
3474                 if (isRouterRecordActivelyScanning) {
3475                     activeScan = true;
3476                     activelyScanningPackages.add(activeRouterRecord.mPackageName);
3477                 }
3478             }
3479             return new RouteDiscoveryPreference.Builder(
3480                             List.copyOf(preferredFeatures), activeScan || shouldForceActiveScan)
3481                     .build();
3482         }
3483 
updateManagerScanningForProviders(boolean isManagerScanning)3484         private void updateManagerScanningForProviders(boolean isManagerScanning) {
3485             for (MediaRoute2Provider provider : mRouteProviders) {
3486                 if (provider instanceof MediaRoute2ProviderServiceProxy) {
3487                     ((MediaRoute2ProviderServiceProxy) provider)
3488                             .setManagerScanning(isManagerScanning);
3489                 }
3490             }
3491         }
3492 
3493         @NonNull
getIndividuallyActiveRouters( MediaRouter2ServiceImpl service, List<RouterRecord> allRouterRecords)3494         private static List<RouterRecord> getIndividuallyActiveRouters(
3495                 MediaRouter2ServiceImpl service, List<RouterRecord> allRouterRecords) {
3496             if (!service.mPowerManager.isInteractive() && !Flags.enableScreenOffScanning()) {
3497                 return Collections.emptyList();
3498             }
3499 
3500             return allRouterRecords.stream()
3501                     .filter(
3502                             record ->
3503                                     isPackageImportanceSufficientForScanning(
3504                                                     service, record.mPackageName)
3505                                             || record.mScanningState
3506                                                     == SCANNING_STATE_SCANNING_FULL)
3507                     .collect(Collectors.toList());
3508         }
3509 
areManagersScanning( MediaRouter2ServiceImpl service, List<ManagerRecord> managerRecords)3510         private static boolean areManagersScanning(
3511                 MediaRouter2ServiceImpl service, List<ManagerRecord> managerRecords) {
3512             if (!service.mPowerManager.isInteractive() && !Flags.enableScreenOffScanning()) {
3513                 return false;
3514             }
3515 
3516             return managerRecords.stream().anyMatch(manager ->
3517                     (manager.mScanningState == SCANNING_STATE_WHILE_INTERACTIVE
3518                             && isPackageImportanceSufficientForScanning(service,
3519                             manager.mOwnerPackageName))
3520                             || manager.mScanningState == SCANNING_STATE_SCANNING_FULL);
3521         }
3522 
isPackageImportanceSufficientForScanning( MediaRouter2ServiceImpl service, String packageName)3523         private static boolean isPackageImportanceSufficientForScanning(
3524                 MediaRouter2ServiceImpl service, String packageName) {
3525             return service.mActivityManager.getPackageImportance(packageName)
3526                     <= REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING;
3527         }
3528 
findProvider(@ullable String providerId)3529         private MediaRoute2Provider findProvider(@Nullable String providerId) {
3530             for (MediaRoute2Provider provider : mRouteProviders) {
3531                 if (TextUtils.equals(provider.getUniqueId(), providerId)) {
3532                     return provider;
3533                 }
3534             }
3535             return null;
3536         }
3537     }
3538 
3539     static final class SessionCreationRequest {
3540         public final RouterRecord mRouterRecord;
3541         public final long mUniqueRequestId;
3542         public final long mManagerRequestId;
3543         public final RoutingSessionInfo mOldSession;
3544         public final MediaRoute2Info mRoute;
3545 
SessionCreationRequest(@onNull RouterRecord routerRecord, long uniqueRequestId, long managerRequestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route)3546         SessionCreationRequest(@NonNull RouterRecord routerRecord, long uniqueRequestId,
3547                 long managerRequestId, @NonNull RoutingSessionInfo oldSession,
3548                 @NonNull MediaRoute2Info route) {
3549             mRouterRecord = routerRecord;
3550             mUniqueRequestId = uniqueRequestId;
3551             mManagerRequestId = managerRequestId;
3552             mOldSession = oldSession;
3553             mRoute = route;
3554         }
3555 
dump(@onNull PrintWriter pw, @NonNull String prefix)3556         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
3557             pw.println(prefix + "SessionCreationRequest");
3558 
3559             String indent = prefix + "  ";
3560 
3561             pw.println(indent + "mUniqueRequestId=" + mUniqueRequestId);
3562             pw.println(indent + "mManagerRequestId=" + mManagerRequestId);
3563             mOldSession.dump(pw, indent);
3564             mRoute.dump(pw, prefix);
3565         }
3566     }
3567 }
3568